diff --git a/DEPS b/DEPS
index a6cc14d..9812685 100644
--- a/DEPS
+++ b/DEPS
@@ -133,11 +133,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '68eb8c276355687ccd3f56f2279232f7fe1a9ebc',
+  'skia_revision': '7fde8e17281023ddfcbd782d4a6badc18341969e',
   # 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': '07e4ce2cfbcb74b9662f379ef8747a25d4c927a6',
+  'v8_revision': 'e89d5754678ef28970176cc03071f4cf108185b1',
   # 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,11 +145,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '3928567b8c5f843d6bba870d0bde05de72cd79e0',
+  'angle_revision': 'ddc4d33a259a5c312d9d4ee30e0875c0590eb70f',
   # 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': '82ae7cbccf2008aec4b34e916a99356c17bb19dd',
+  'swiftshader_revision': 'c57ec6f0af339ba3af1debdf17aee2cc0b115362',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -200,7 +200,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': '23edc497039adbbf7453107cd9ee68133b45b98d',
+  'catapult_revision': 'bf6e6c9070d3aa14f88013d36cf96b251cbf4c86',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -256,7 +256,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '6d04da22c6c7362ac6dd55b291a22cb21303ffe6',
+  'spv_tools_revision': '5fc5303eeceb82df64abb6db4bbdf3b55a6cd591',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -268,11 +268,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'shaderc_revision': '6805e5544d6c3733e941754376f44d0d5b61309f',
+  'shaderc_revision': 'afc69d368b03708869178d33c6832758635b9f62',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '94274b63fb2230c0e18261b3bb985e1b6aff56ee',
+  'dawn_revision': 'e99e2408e96dbca97a4c52beb8c212457fe3306f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -476,7 +476,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '3afe64494ecf45b4165d3bacabe9dea35cb001c5',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '17f8a299668092b0b3904db64a425556d4ebd826',
       'condition': 'checkout_ios',
   },
 
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'e3fc3ca79abb0f0088a419f124a5e643c596d6fb',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'e2f2fee059f6a3b1bdc8ece4315c395e0ef592f1',
       'condition': 'checkout_linux',
   },
 
@@ -831,7 +831,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0647cd502cea650b29b33c0082dcc5c836eb2c39',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9c0620120980e4c247ff8325ee8bbdcd4d9576e0',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1184,7 +1184,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '7a3221f8bedc119e4c756ca54871d96aad132c12',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'c575dd3cd0d2990b8b52f5d3c7c8520b0a76f5a0',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1396,7 +1396,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3e41647c15c8cc57d6c15fd9d4555c430ad13a5c',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@71ee4b946e64eabaa165b9509fc7f648a033d413',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 2bfb63e..0c4a26e 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2501,6 +2501,7 @@
                  'stevenjb+watch-md-settings@chromium.org',
                  'hsuregan+watch@chromium.org',
                  'jamescook+watch@chromium.org',
+                 'jhawkins+watch@chromium.org',
                  'jordynass+watch@chromium.org',
                  'maybelle+watch@chromium.org'],
     'settings_forked_os_settings': [
diff --git a/android_webview/browser/renderer_host/auto_login_parser.cc b/android_webview/browser/renderer_host/auto_login_parser.cc
index 3d613e4..928fcc9 100644
--- a/android_webview/browser/renderer_host/auto_login_parser.cc
+++ b/android_webview/browser/renderer_host/auto_login_parser.cc
@@ -54,8 +54,7 @@
        ++it) {
     const std::string& key = it->first;
     const std::string& value = it->second;
-    std::string unescaped_value;
-    net::UnescapeBinaryURLComponent(value, &unescaped_value);
+    std::string unescaped_value = net::UnescapeBinaryURLComponent(value);
     if (key == "realm") {
       if (!MatchRealm(unescaped_value, realm_restriction))
         return false;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
index 19443ce..9f395a6 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
@@ -372,6 +372,10 @@
         AwWebResourceResponse response = new AwWebResourceResponse(
                 mimeType, encoding, null, statusCode, reasonPhrase, responseHeaders);
         mClient.getCallbackHelper().postOnReceivedHttpError(request, response);
+
+        // Record UMA on http response status.
+        RecordHistogram.recordSparseHistogram(
+                "Android.WebView.onReceivedHttpError.StatusCode", statusCode);
     }
 
     @CalledByNativeUnchecked
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index c43e36f..92a40ed 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -44,12 +44,13 @@
     "frame/non_client_frame_view_ash.h",
     "frame/wide_frame_view.h",
     "magnifier/magnification_controller.h",
-    "multi_user/multi_user_window_manager_delegate_classic.h",
 
     # TODO: move MultiUserWindowManager (and delegate) to sources:
     # https://crbug.com/756085
     "multi_user/multi_user_window_manager_impl.h",
     "new_window_controller.h",
+    "public/cpp/multi_user_window_manager.h",
+    "public/cpp/multi_user_window_manager_delegate.h",
     "root_window_controller.h",
     "screenshot_delegate.h",
     "session/session_controller.h",
@@ -1269,8 +1270,6 @@
     "ws/ash_gpu_interface_provider.h",
     "ws/ash_window_manager.cc",
     "ws/ash_window_manager.h",
-    "ws/multi_user_window_manager_bridge.cc",
-    "ws/multi_user_window_manager_bridge.h",
     "ws/window_lookup.cc",
     "ws/window_service_delegate_impl.cc",
     "ws/window_service_delegate_impl.h",
@@ -1625,7 +1624,6 @@
     "assistant/util/deep_link_util_unittest.cc",
     "autoclick/autoclick_drag_event_rewriter_unittest.cc",
     "autoclick/autoclick_unittest.cc",
-    "cursor_unittest.cc",
     "dbus/url_handler_service_provider_unittest.cc",
     "detachable_base/detachable_base_handler_unittest.cc",
     "detachable_base/detachable_base_notification_controller_unittest.cc",
@@ -1862,7 +1860,6 @@
     "wm/lock_state_controller_unittest.cc",
     "wm/mru_window_tracker_unittest.cc",
     "wm/native_cursor_manager_ash_unittest.cc",
-    "wm/non_client_frame_controller_unittest.cc",
     "wm/overlay_event_filter_unittest.cc",
     "wm/overlay_layout_manager_unittest.cc",
     "wm/overview/cleanup_animation_observer_unittest.cc",
@@ -1889,7 +1886,6 @@
     "wm/tablet_mode/accelerometer_test_data_literals.cc",
     "wm/tablet_mode/tablet_mode_controller_unittest.cc",
     "wm/tablet_mode/tablet_mode_window_manager_unittest.cc",
-    "wm/top_level_window_factory_unittest.cc",
     "wm/toplevel_window_event_handler_unittest.cc",
     "wm/video_detector_unittest.cc",
     "wm/window_animations_unittest.cc",
@@ -1910,9 +1906,6 @@
     "wm/workspace/workspace_layout_manager_unittest.cc",
     "wm/workspace/workspace_window_resizer_unittest.cc",
     "wm/workspace_controller_unittest.cc",
-    "ws/ash_window_manager_unittest.cc",
-    "ws/window_lookup_unittest.cc",
-    "ws/window_service_delegate_impl_unittest.cc",
   ]
   configs += [
     "//build/config:precompiled_headers",
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 5aa215e..14d710a2 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -48,6 +48,7 @@
 #include "base/stl_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "media/base/media_switches.h"
 #include "services/media_session/public/cpp/test/test_media_controller.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
diff --git a/ash/accelerators/accelerator_unittest.cc b/ash/accelerators/accelerator_unittest.cc
index 19cb447c..6232102 100644
--- a/ash/accelerators/accelerator_unittest.cc
+++ b/ash/accelerators/accelerator_unittest.cc
@@ -20,8 +20,6 @@
 #include "base/test/metrics/user_action_tester.h"
 #include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/network/network_handler.h"
-#include "services/ws/public/mojom/window_tree_constants.mojom.h"
-#include "services/ws/test_window_tree_client.h"
 #include "ui/aura/window.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/test_accelerator_target.h"
@@ -206,31 +204,4 @@
   GetAppListTestHelper()->CheckVisibility(false);
 }
 
-// This is meant to exercise an end to end test of an accelerator that happens
-// *after* the remote client is given a chance to handle it.
-TEST_F(AcceleratorTest, PostAcceleratorWorks) {
-  // Register a post-accelerator. That is, an accelerator that is handled
-  // *after* the remote client (focused target) is given a chance.
-  ui::TestAcceleratorTarget test_target;
-  const ui::KeyboardCode accelerator_code = ui::VKEY_N;
-  const int accelerator_modifiers = ui::EF_CONTROL_DOWN;
-  Shell::Get()->accelerator_controller()->Register(
-      {ui::Accelerator(accelerator_code, accelerator_modifiers)}, &test_target);
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  window->Focus();
-  ASSERT_TRUE(window->HasFocus());
-  GetEventGenerator()->PressKey(accelerator_code, accelerator_modifiers);
-
-  // The accelerator was not pressed yet (the KeyEvent was sent to the client,
-  // but the client hasn't responded).
-  EXPECT_EQ(0, test_target.accelerator_count());
-
-  EXPECT_TRUE(GetTestWindowTreeClient()->AckFirstEvent(
-      GetWindowTree(), ws::mojom::EventResult::UNHANDLED));
-
-  // The client didn't handle the event, so |test_target| should get the
-  // accelerator.
-  EXPECT_EQ(1, test_target.accelerator_count());
-}
-
 }  // namespace ash
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index de11b69..40fdaba 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -541,10 +541,6 @@
     UMA_HISTOGRAM_ENUMERATION(app_list::kAppListToggleMethodHistogram,
                               show_source);
   }
-
-  for (auto& observer : observers_)
-    observer.OnAppListToggled();
-
   return action;
 }
 
diff --git a/ash/app_list/app_list_controller_observer.h b/ash/app_list/app_list_controller_observer.h
index 316fb12..920e69b 100644
--- a/ash/app_list/app_list_controller_observer.h
+++ b/ash/app_list/app_list_controller_observer.h
@@ -14,9 +14,6 @@
  public:
   // Called when the AppList is shown or dismissed.
   virtual void OnAppListVisibilityChanged(bool shown, int64_t display_id) {}
-
-  // Called when the AppList button is toggled.
-  virtual void OnAppListToggled() {}
 };
 
 }  // namespace ash
diff --git a/ash/autoclick/autoclick_unittest.cc b/ash/autoclick/autoclick_unittest.cc
index 11949f24..9d4821a 100644
--- a/ash/autoclick/autoclick_unittest.cc
+++ b/ash/autoclick/autoclick_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/system/accessibility/autoclick_menu_view.h"
 #include "ash/test/ash_test_base.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "ui/accessibility/accessibility_switches.h"
 #include "ui/aura/test/test_window_delegate.h"
diff --git a/ash/cursor_unittest.cc b/ash/cursor_unittest.cc
deleted file mode 100644
index 580de590..0000000
--- a/ash/cursor_unittest.cc
+++ /dev/null
@@ -1,115 +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 "ash/test/ash_test_base.h"
-
-#include "ash/shell.h"
-#include "services/ws/test_window_tree_client.h"
-#include "services/ws/window_tree_test_helper.h"
-#include "ui/aura/client/cursor_client.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_delegate.h"
-#include "ui/events/event.h"
-#include "ui/events/event_constants.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/gfx/image/image_unittest_util.h"
-
-namespace ash {
-
-using CursorTest = AshTestBase;
-
-TEST_F(CursorTest, TopLevel) {
-  // Create a top level window.
-  std::unique_ptr<aura::Window> window =
-      CreateTestWindow(gfx::Rect(0, 0, 100, 100));
-
-  // Make sure the WindowTreeClient receives events.
-  EXPECT_EQ(0U, GetTestWindowTreeClient()->input_events().size());
-  ui::test::EventGenerator generator(window->GetRootWindow());
-  generator.MoveMouseToInHost(50, 50);
-  ASSERT_EQ(1U, GetTestWindowTreeClient()->input_events().size());
-  EXPECT_EQ(ui::EventType::ET_MOUSE_MOVED,
-            GetTestWindowTreeClient()->PopInputEvent().event->type());
-
-  // Check that WindowTree actually sets the cursor.
-  aura::client::CursorClient* cursor_client =
-      aura::client::GetCursorClient(window->GetRootWindow());
-  const ui::Cursor help_cursor(ui::CursorType::kHelp);
-  GetWindowTreeTestHelper()->SetCursor(window.get(), help_cursor);
-  EXPECT_EQ(ui::CursorType::kHelp,
-            window->delegate()->GetCursor({}).native_type());
-  EXPECT_EQ(ui::CursorType::kHelp, cursor_client->GetCursor().native_type());
-
-  // If the mouse is not over the host, then SetCursor won't update the actual
-  // cursor (i.e. the CursorClient).
-  generator.MoveMouseToInHost(500, 500);
-  const ui::Cursor not_allowed_cursor(ui::CursorType::kNotAllowed);
-  GetWindowTreeTestHelper()->SetCursor(window.get(), not_allowed_cursor);
-  EXPECT_EQ(ui::CursorType::kNotAllowed,
-            window->delegate()->GetCursor({}).native_type());
-  EXPECT_NE(ui::CursorType::kNotAllowed,
-            cursor_client->GetCursor().native_type());
-}
-
-TEST_F(CursorTest, Embedded) {
-  // Create a window to hold an embedding and set its cursor.
-  aura::Window* embed_root = GetWindowTreeTestHelper()->NewWindow();
-  ws::TestWindowTreeClient test_client;
-  GetWindowTreeTestHelper()->Embed(embed_root, nullptr, &test_client, 0);
-  const ui::Cursor help_cursor(ui::CursorType::kHelp);
-  GetWindowTreeTestHelper()->SetCursor(embed_root, help_cursor);
-
-  // Since the window isn't visible, the actual cursor shouldn't have changed.
-  EXPECT_FALSE(embed_root->IsVisible());
-  EXPECT_NE(ui::CursorType::kHelp,
-            ash::Shell::Get()->cursor_manager()->GetCursor().native_type());
-
-  // Create a top level window and put the embed root in it.
-  std::unique_ptr<aura::Window> toplevel =
-      CreateTestWindow(gfx::Rect(0, 0, 100, 100));
-  toplevel->AddChild(embed_root);
-  embed_root->SetBounds(toplevel->GetTargetBounds());
-  embed_root->Show();
-  EXPECT_TRUE(embed_root->IsVisible());
-
-  // Now put the cursor over it and the previously set cursor should be used.
-  ui::test::EventGenerator generator(toplevel->GetRootWindow());
-  generator.MoveMouseToInHost(50, 50);
-  EXPECT_EQ(ui::CursorType::kHelp,
-            ash::Shell::Get()->cursor_manager()->GetCursor().native_type());
-
-  // Setting to a new cursor should also immediately update the actual cursor.
-  const ui::Cursor not_allowed_cursor(ui::CursorType::kNotAllowed);
-  GetWindowTreeTestHelper()->SetCursor(embed_root, not_allowed_cursor);
-  EXPECT_EQ(ui::CursorType::kNotAllowed,
-            ash::Shell::Get()->cursor_manager()->GetCursor().native_type());
-}
-
-TEST_F(CursorTest, Custom) {
-  // Create and hover a window.
-  std::unique_ptr<aura::Window> window =
-      CreateTestWindow(gfx::Rect(0, 0, 100, 100));
-  EXPECT_EQ(0U, GetTestWindowTreeClient()->input_events().size());
-  ui::test::EventGenerator generator(window->GetRootWindow());
-  generator.MoveMouseToInHost(50, 50);
-
-  // Set a custom cursor.
-  SkBitmap bitmap = gfx::test::CreateBitmap(10, 10);
-  ui::Cursor image_cursor(ui::CursorType::kCustom);
-  image_cursor.set_custom_hotspot(gfx::Point(1, 4));
-  image_cursor.set_custom_bitmap(bitmap);
-  image_cursor.set_device_scale_factor(1.f);
-  GetWindowTreeTestHelper()->SetCursor(window.get(), image_cursor);
-
-  // Make sure it worked.
-  EXPECT_EQ(ui::CursorType::kCustom,
-            window->delegate()->GetCursor({}).native_type());
-  aura::client::CursorClient* cursor_client =
-      aura::client::GetCursorClient(window->GetRootWindow());
-  EXPECT_EQ(ui::CursorType::kCustom, cursor_client->GetCursor().native_type());
-  EXPECT_EQ(bitmap.getGenerationID(),
-            cursor_client->GetCursor().GetBitmap().getGenerationID());
-}
-
-}  // namespace ash
diff --git a/ash/display/window_tree_host_manager_unittest.cc b/ash/display/window_tree_host_manager_unittest.cc
index 04b2102..fe85b10 100644
--- a/ash/display/window_tree_host_manager_unittest.cc
+++ b/ash/display/window_tree_host_manager_unittest.cc
@@ -22,7 +22,6 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
-#include "services/ws/test_window_tree_client.h"
 #include "ui/aura/client/focus_change_observer.h"
 #include "ui/aura/client/focus_client.h"
 #include "ui/aura/env.h"
@@ -1087,59 +1086,6 @@
   }
 }
 
-// Tests that SetPrimaryDisplayId updates the Window Service client.
-TEST_F(WindowTreeHostManagerTest, SetPrimaryDisplayIdUpdateWSClient) {
-  // Create two displays.
-  UpdateDisplay("200x200,300x300");
-  ASSERT_EQ(2200000000, display::Screen::GetScreen()->GetPrimaryDisplay().id());
-  int64_t non_primary_display_id =
-      display_manager()->GetSecondaryDisplay().id();
-  ASSERT_EQ(2200000001, non_primary_display_id);
-
-  std::vector<ws::Change>* ws_client_changes =
-      GetTestWindowTreeClient()->tracker()->changes();
-
-  // Create the first window (0,1) on primary display 2200000000.
-  ws_client_changes->clear();
-  std::unique_ptr<aura::Window> window_in_primary =
-      CreateTestWindow(gfx::Rect(0, 0, 30, 40));
-  auto iter = FirstChangeOfType(*ws_client_changes,
-                                ws::CHANGE_TYPE_ON_TOP_LEVEL_CREATED);
-  ASSERT_NE(iter, ws_client_changes->end());
-  ASSERT_EQ(1, static_cast<int>(iter->window_id));
-  ASSERT_EQ(2200000000, iter->display_id);
-
-  // Create the second window (0,2) on non-primary display 2200000001.
-  ws_client_changes->clear();
-  std::unique_ptr<aura::Window> window_in_non_primary =
-      CreateTestWindow(gfx::Rect(300, 0, 50, 60));
-  iter = FirstChangeOfType(*ws_client_changes,
-                           ws::CHANGE_TYPE_ON_TOP_LEVEL_CREATED);
-  ASSERT_NE(iter, ws_client_changes->end());
-  ASSERT_EQ(2, static_cast<int>(iter->window_id));
-  ASSERT_EQ(2200000001, iter->display_id);
-
-  // Set primary display id to the non-primary 2200000001. This triggers
-  // swapping of WindowTreeHosts and client should receive updates about the
-  // display id change and bounds change if their screen bounds is changed..
-  ws_client_changes->clear();
-  Shell::Get()->window_tree_host_manager()->SetPrimaryDisplayId(
-      non_primary_display_id);
-  EXPECT_TRUE(
-      ContainsChange(*ws_client_changes,
-                     "DisplayChanged window_id=0,1 display_id=2200000001"));
-  EXPECT_TRUE(
-      ContainsChange(*ws_client_changes,
-                     "DisplayChanged window_id=0,2 display_id=2200000000"));
-
-  // Window (0,2) on non-primary display changes its screen bounds. But
-  // window (0,1) on the primary display does not because the primary display
-  // origin is always (0,0).
-  EXPECT_TRUE(ContainsChange(
-      *ws_client_changes,
-      "BoundsChanged window=0,2 bounds=-100,0 50x60 local_surface_id=*"));
-}
-
 TEST_F(WindowTreeHostManagerTest, OverscanInsets) {
   WindowTreeHostManager* window_tree_host_manager =
       Shell::Get()->window_tree_host_manager();
diff --git a/ash/home_screen/home_launcher_gesture_handler.cc b/ash/home_screen/home_launcher_gesture_handler.cc
index ef1b545..ed0c1f79 100644
--- a/ash/home_screen/home_launcher_gesture_handler.cc
+++ b/ash/home_screen/home_launcher_gesture_handler.cc
@@ -626,10 +626,11 @@
   std::unique_ptr<ui::ScopedLayerAnimationSettings> overview_settings;
   if (overview_active_on_gesture_start_ && controller->IsSelecting()) {
     DCHECK_EQ(mode_, Mode::kSlideUpToShow);
+    const int inverted_y_position = gfx::Tween::IntValueBetween(
+        progress, work_area.y(), work_area.bottom());
     overview_settings =
         controller->overview_session()->UpdateGridAtLocationYPositionAndOpacity(
-            display_.id(), y_position - work_area.height(), 1.f - opacity,
-            work_area,
+            display_.id(), inverted_y_position, 1.f - opacity,
             animate ? base::BindRepeating(
                           &HomeLauncherGestureHandler::UpdateSettings,
                           base::Unretained(this))
diff --git a/ash/multi_user/multi_user_window_manager_delegate_classic.h b/ash/multi_user/multi_user_window_manager_delegate_classic.h
deleted file mode 100644
index 167fd957..0000000
--- a/ash/multi_user/multi_user_window_manager_delegate_classic.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_DELEGATE_CLASSIC_H_
-#define ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_DELEGATE_CLASSIC_H_
-
-#include "ash/ash_export.h"
-
-class AccountId;
-
-namespace aura {
-class Window;
-}  // namespace aura
-
-namespace ash {
-
-// Delegate used in classic and single-process mash mode. This delegate is
-// notified when the state of a window changes. In classic mode, this delegate
-// is called for all windows, in single-process mash mode, this delegate is
-// called for Windows created in ash's window hierarchy (more specifically,
-// Windows whose aura::Env has a mode of aura::Mode::LOCAL). For example, Arc
-// creates Windows that are parented directly to Ash's window hierarchy, changes
-// to such windows are called through this delegate.
-class ASH_EXPORT MultiUserWindowManagerDelegateClassic {
- public:
-  // Called when the owner of the window tracked by the manager is changed.
-  // |was_minimized| is true if the window was minimized. |teleported| is true
-  // if the window was not on the desktop of the current user.
-  virtual void OnOwnerEntryChanged(aura::Window* window,
-                                   const AccountId& account_id,
-                                   bool was_minimized,
-                                   bool teleported) {}
-
- protected:
-  virtual ~MultiUserWindowManagerDelegateClassic() {}
-};
-
-}  // namespace ash
-
-#endif  // ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_DELEGATE_CLASSIC_H_
diff --git a/ash/multi_user/multi_user_window_manager_impl.cc b/ash/multi_user/multi_user_window_manager_impl.cc
index a1fa38b..ac55062 100644
--- a/ash/multi_user/multi_user_window_manager_impl.cc
+++ b/ash/multi_user/multi_user_window_manager_impl.cc
@@ -8,16 +8,14 @@
 #include <vector>
 
 #include "ash/media/media_controller.h"
-#include "ash/multi_user/multi_user_window_manager_delegate_classic.h"
 #include "ash/multi_user/user_switch_animator.h"
+#include "ash/public/cpp/multi_user_window_manager_delegate.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/auto_reset.h"
 #include "base/macros.h"
-#include "ui/aura/mus/window_mus.h"
-#include "ui/aura/mus/window_tree_client.h"
 #include "ui/aura/window.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/events/event.h"
@@ -116,22 +114,16 @@
 };
 
 MultiUserWindowManagerImpl::WindowEntry::WindowEntry(
-    const AccountId& account_id,
-    base::Optional<ws::Id> window_id)
-    : owner_(account_id),
-      show_for_user_(account_id),
-      window_id_(std::move(window_id)),
-      from_window_service_(window_id.has_value()) {}
+    const AccountId& account_id)
+    : owner_(account_id), show_for_user_(account_id) {}
 
 MultiUserWindowManagerImpl::WindowEntry::~WindowEntry() = default;
 
 MultiUserWindowManagerImpl::MultiUserWindowManagerImpl(
-    mojom::MultiUserWindowManagerClient* client,
-    MultiUserWindowManagerDelegateClassic* classic_delegate,
+    MultiUserWindowManagerDelegate* delegate,
     const AccountId& account_id)
-    : client_(client),
-      classic_delegate_(classic_delegate),
-      current_account_id_(account_id) {
+    : delegate_(delegate), current_account_id_(account_id) {
+  DCHECK(delegate_);
   g_instance = this;
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
   Shell::Get()->session_controller()->AddObserver(this);
@@ -163,21 +155,9 @@
   return g_instance;
 }
 
-void MultiUserWindowManagerImpl::SetClient(
-    mojom::MultiUserWindowManagerClient* client) {
-  client_ = client;
-
-  // Window ids are unique to a particular client. If the client changes, drop
-  // any existing ids.
-  for (auto& pair : window_to_entry_)
-    pair.second->reset_window_id();
-}
-
-void MultiUserWindowManagerImpl::SetWindowOwner(
-    aura::Window* window,
-    const AccountId& account_id,
-    bool show_for_current_user,
-    base::Optional<ws::Id> window_id) {
+void MultiUserWindowManagerImpl::SetWindowOwner(aura::Window* window,
+                                                const AccountId& account_id,
+                                                bool show_for_current_user) {
   // Make sure the window is valid and there was no owner yet.
   DCHECK(window);
   DCHECK(account_id.is_valid());
@@ -191,7 +171,7 @@
 
   DCHECK(GetWindowOwner(window).empty());
   std::unique_ptr<WindowEntry> window_entry_ptr =
-      std::make_unique<WindowEntry>(account_id, std::move(window_id));
+      std::make_unique<WindowEntry>(account_id);
   WindowEntry* window_entry = window_entry_ptr.get();
   window_to_entry_[window] = std::move(window_entry_ptr);
 
@@ -215,12 +195,6 @@
     SetWindowVisibility(window, false);
 }
 
-const AccountId& MultiUserWindowManagerImpl::GetWindowOwner(
-    aura::Window* window) const {
-  WindowToEntryMap::const_iterator it = window_to_entry_.find(window);
-  return it != window_to_entry_.end() ? it->second->owner() : EmptyAccountId();
-}
-
 void MultiUserWindowManagerImpl::ShowWindowForUser(
     aura::Window* window,
     const AccountId& account_id) {
@@ -237,6 +211,13 @@
   Shell::Get()->session_controller()->SwitchActiveUser(account_id);
 }
 
+const AccountId& MultiUserWindowManagerImpl::GetWindowOwner(
+    const aura::Window* window) const {
+  WindowToEntryMap::const_iterator it =
+      window_to_entry_.find(const_cast<aura::Window*>(window));
+  return it != window_to_entry_.end() ? it->second->owner() : EmptyAccountId();
+}
+
 bool MultiUserWindowManagerImpl::AreWindowsSharedAmongUsers() const {
   for (auto& window_pair : window_to_entry_) {
     if (window_pair.second->owner() != window_pair.second->show_for_user())
@@ -245,6 +226,29 @@
   return false;
 }
 
+std::set<AccountId> MultiUserWindowManagerImpl::GetOwnersOfVisibleWindows()
+    const {
+  std::set<AccountId> result;
+  for (auto& window_pair : window_to_entry_) {
+    if (window_pair.first->IsVisible())
+      result.insert(window_pair.second->owner());
+  }
+  return result;
+}
+
+const AccountId& MultiUserWindowManagerImpl::GetUserPresentingWindow(
+    const aura::Window* window) const {
+  auto iter = window_to_entry_.find(const_cast<aura::Window*>(window));
+  // If the window is not owned by anyone it is shown on all desktops and we
+  // return the empty string.
+  return (iter == window_to_entry_.end()) ? EmptyAccountId()
+                                          : iter->second->show_for_user();
+}
+
+const AccountId& MultiUserWindowManagerImpl::CurrentAccountId() const {
+  return current_account_id_;
+}
+
 bool MultiUserWindowManagerImpl::IsWindowOnDesktopOfUser(
     aura::Window* window,
     const AccountId& account_id) const {
@@ -277,9 +281,6 @@
   // This needs to be set before the animation starts.
   current_account_id_ = account_id;
 
-  if (client_)
-    client_->OnWillSwitchActiveAccount(current_account_id_);
-
   // Here to avoid a very nasty race condition, we must destruct any previously
   // created animation before creating a new one. Otherwise, the newly
   // constructed will hide all windows of the old user in the first step of the
@@ -420,14 +421,8 @@
     SetWindowVisibility(window, false, kTeleportAnimationTime);
   }
 
-  // Notify entry change.
-  if (!window_entry->from_window_service()) {
-    classic_delegate_->OnOwnerEntryChanged(window, account_id, minimized,
-                                           teleported);
-  } else if (client_ && window_entry->window_id().has_value()) {
-    client_->OnWindowOwnerEntryChanged(*window_entry->window_id(), account_id,
-                                       minimized, teleported);
-  }
+  delegate_->OnWindowOwnerEntryChanged(window, account_id, minimized,
+                                       teleported);
   return true;
 }
 
@@ -576,4 +571,11 @@
                     : base::TimeDelta());
 }
 
+// static
+std::unique_ptr<MultiUserWindowManager> MultiUserWindowManager::Create(
+    MultiUserWindowManagerDelegate* delegate,
+    const AccountId& account_id) {
+  return std::make_unique<MultiUserWindowManagerImpl>(delegate, account_id);
+}
+
 }  // namespace ash
diff --git a/ash/multi_user/multi_user_window_manager_impl.h b/ash/multi_user/multi_user_window_manager_impl.h
index 79d47c5..39f5dc9 100644
--- a/ash/multi_user/multi_user_window_manager_impl.h
+++ b/ash/multi_user/multi_user_window_manager_impl.h
@@ -9,21 +9,19 @@
 #include <memory>
 
 #include "ash/ash_export.h"
-#include "ash/public/interfaces/multi_user_window_manager.mojom.h"
+#include "ash/public/cpp/multi_user_window_manager.h"
 #include "ash/session/session_observer.h"
 #include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
-#include "base/optional.h"
 #include "base/time/time.h"
 #include "components/account_id/account_id.h"
-#include "services/ws/common/types.h"
 #include "ui/aura/window_observer.h"
 #include "ui/wm/core/transient_window_observer.h"
 
 namespace ash {
 
-class MultiUserWindowManagerDelegateClassic;
+class MultiUserWindowManagerDelegate;
 class UserSwitchAnimator;
 
 // MultiUserWindowManager associates windows with users and ensures the
@@ -37,12 +35,6 @@
 // account are the same, but the user may choose to show a window from an other
 // account, in which case the 'shown' account changes.
 //
-// MultiUserWindowManager makes use of the following client/delegate interfaces
-// mojom::MultiUserWindowManagerClient: used for windows created by the window
-// service, as well as major state changes (such as animation changing).
-// MultiUserWindowManagerDelegateClassic: used for all other windows. See
-// MultiUserWindowManagerDelegateClassic for details on what this means.
-//
 // Note:
 // - aura::Window::Hide() is currently hiding the window and all owned transient
 //   children. However aura::Window::Show() is only showing the window itself.
@@ -52,7 +44,8 @@
 //   changed back to its requested state upon showing by us - or when the window
 //   gets detached from its current owning parent.
 class ASH_EXPORT MultiUserWindowManagerImpl
-    : public SessionObserver,
+    : public MultiUserWindowManager,
+      public SessionObserver,
       public aura::WindowObserver,
       public ::wm::TransientWindowObserver,
       public TabletModeObserver {
@@ -64,38 +57,24 @@
     ANIMATION_SPEED_DISABLED  // Unit tests which do not require animations.
   };
 
-  MultiUserWindowManagerImpl(
-      mojom::MultiUserWindowManagerClient* client,
-      MultiUserWindowManagerDelegateClassic* classic_delegate,
-      const AccountId& account_id);
+  MultiUserWindowManagerImpl(MultiUserWindowManagerDelegate* delegate,
+                             const AccountId& account_id);
   ~MultiUserWindowManagerImpl() override;
 
   static MultiUserWindowManagerImpl* Get();
 
-  // Resets the client. This is called when running in mash. In single-process
-  // mash, the browser creates this class (with no client) and
-  // MultiUserWindowManagerBridge sets the client (as the client is provided
-  // over mojom). In multi-process mash, MultiUserWindowManagerBridge creates
-  // this and sets the client. This function is only necessary until
-  // multi-process mash is the default.
-  void SetClient(mojom::MultiUserWindowManagerClient* client);
-
-  // Associates a window with a particular account. This may result in hiding
-  // |window|. This should *not* be called more than once with a different
-  // account. If |show_for_current_user| is true, this sets the 'shown'
-  // account to the current account. If |window_id| is valid, changes to
-  // |window| are notified through MultiUserWindowManagerClient. If |window_id|
-  // is empty, MultiUserWindowManagerDelegateClassic is used.
+  // MultiUserWindowManager:
   void SetWindowOwner(aura::Window* window,
                       const AccountId& account_id,
-                      bool show_for_current_user,
-                      base::Optional<ws::Id> window_id = base::nullopt);
-
-  // Sets the 'shown' account for a window. See class description for details on
-  // what the 'shown' account is. This function may trigger changing the active
-  // user. When the window is minimized, the 'shown' account is reset to the
-  // 'owning' account.
-  void ShowWindowForUser(aura::Window* window, const AccountId& account_id);
+                      bool show_for_current_user) override;
+  void ShowWindowForUser(aura::Window* window,
+                         const AccountId& account_id) override;
+  const AccountId& GetWindowOwner(const aura::Window* window) const override;
+  bool AreWindowsSharedAmongUsers() const override;
+  std::set<AccountId> GetOwnersOfVisibleWindows() const override;
+  const AccountId& GetUserPresentingWindow(
+      const aura::Window* window) const override;
+  const AccountId& CurrentAccountId() const override;
 
   // SessionObserver:
   void OnActiveUserSessionChanged(const AccountId& account_id) override;
@@ -129,7 +108,7 @@
 
   class WindowEntry {
    public:
-    WindowEntry(const AccountId& account_id, base::Optional<ws::Id> window_id);
+    explicit WindowEntry(const AccountId& account_id);
     ~WindowEntry();
 
     // Returns the owner of this window. This cannot be changed.
@@ -150,16 +129,6 @@
     // Sets if the window gets shown for the active user or not.
     void set_show(bool show) { show_ = show; }
 
-    // True if this window was created by the window service.
-    bool from_window_service() const { return from_window_service_; }
-
-    // Unsets the |window_id|. This does not effect whether the window is
-    // from the window-service, only the stored id. Resetting the id happens
-    // when the client changes. This is necessary as the id is generally unique
-    // to a client.
-    void reset_window_id() { window_id_.reset(); }
-    const base::Optional<ws::Id> window_id() const { return window_id_; }
-
    private:
     // The user id of the owner of this window.
     const AccountId owner_;
@@ -170,11 +139,6 @@
     // True if the window should be visible for the user which shows the window.
     bool show_ = true;
 
-    // The id assigned to the window by the WindowService.
-    base::Optional<ws::Id> window_id_;
-
-    const bool from_window_service_;
-
     DISALLOW_COPY_AND_ASSIGN(WindowEntry);
   };
 
@@ -183,13 +147,6 @@
   using WindowToEntryMap =
       std::map<aura::Window*, std::unique_ptr<WindowEntry>>;
 
-  const AccountId& GetWindowOwner(aura::Window* window) const;
-
-  // Returns true if at least one window's 'owner' account differs from its
-  // 'shown' account. In other words, a window from one account is shown with
-  // windows from another account.
-  bool AreWindowsSharedAmongUsers() const;
-
   // Returns true if the 'shown' owner of |window| is |account_id|.
   bool IsWindowOnDesktopOfUser(aura::Window* window,
                                const AccountId& account_id) const;
@@ -243,9 +200,7 @@
   // Returns the time for an animation.
   base::TimeDelta GetAdjustedAnimationTime(base::TimeDelta default_time) const;
 
-  mojom::MultiUserWindowManagerClient* client_;
-
-  MultiUserWindowManagerDelegateClassic* classic_delegate_;
+  MultiUserWindowManagerDelegate* delegate_;
 
   // A lookup to see to which user the given window belongs to, where and if it
   // should get shown.
diff --git a/ash/multi_user/user_switch_animator.cc b/ash/multi_user/user_switch_animator.cc
index c4a35f0b..f44ce0e 100644
--- a/ash/multi_user/user_switch_animator.cc
+++ b/ash/multi_user/user_switch_animator.cc
@@ -5,6 +5,7 @@
 #include "ash/multi_user/user_switch_animator.h"
 
 #include "ash/multi_user/multi_user_window_manager_impl.h"
+#include "ash/public/cpp/multi_user_window_manager_delegate.h"
 #include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -140,8 +141,7 @@
     case ANIMATION_STEP_FINALIZE:
       user_changed_animation_timer_.reset();
       animation_step_ = ANIMATION_STEP_ENDED;
-      if (owner_->client_)
-        owner_->client_->OnDidSwitchActiveAccount();
+      owner_->delegate_->OnDidSwitchActiveAccount();
       break;
     case ANIMATION_STEP_ENDED:
       NOTREACHED();
@@ -197,8 +197,7 @@
   if (animation_step != ANIMATION_STEP_SHOW_NEW_USER)
     return;
 
-  if (owner_->client_)
-    owner_->client_->OnTransitionUserShelfToNewAccount();
+  owner_->delegate_->OnTransitionUserShelfToNewAccount();
 }
 
 void UserSwitchAnimator::TransitionWindows(AnimationStep animation_step) {
diff --git a/ash/public/cpp/multi_user_window_manager.h b/ash/public/cpp/multi_user_window_manager.h
new file mode 100644
index 0000000..9b2df59
--- /dev/null
+++ b/ash/public/cpp/multi_user_window_manager.h
@@ -0,0 +1,69 @@
+// 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_MULTI_USER_WINDOW_MANAGER_H_
+#define ASH_PUBLIC_CPP_MULTI_USER_WINDOW_MANAGER_H_
+
+#include <memory>
+#include <set>
+
+#include "ash/ash_export.h"
+
+class AccountId;
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+
+class MultiUserWindowManagerDelegate;
+
+// Used to assign windows to user accounts so that ash shows the appropriate set
+// of windows based on the active user.
+class ASH_EXPORT MultiUserWindowManager {
+ public:
+  static std::unique_ptr<MultiUserWindowManager> Create(
+      MultiUserWindowManagerDelegate* delegate,
+      const AccountId& account_id);
+
+  virtual ~MultiUserWindowManager() {}
+
+  // Associates a window with a particular account. This may result in hiding
+  // |window|. This should *not* be called more than once with a different
+  // account. If |show_for_current_user| is true, this sets the 'shown'
+  // account to the current account.
+  virtual void SetWindowOwner(aura::Window* window,
+                              const AccountId& account_id,
+                              bool show_for_current_user) = 0;
+
+  // Shows a previously registered window for the specified account.
+  virtual void ShowWindowForUser(aura::Window* window,
+                                 const AccountId& account_id) = 0;
+
+  virtual const AccountId& GetWindowOwner(const aura::Window* window) const = 0;
+
+  // Returns true if at least one window's 'owner' account differs from its
+  // 'shown' account. In other words, a window from one account is shown with
+  // windows from another account.
+  virtual bool AreWindowsSharedAmongUsers() const = 0;
+
+  // Returns the set owners for the visible windows.
+  virtual std::set<AccountId> GetOwnersOfVisibleWindows() const = 0;
+
+  // Returns the user for which the window is currently shown. An empty
+  // AccountId() is returned if the window is presented for every user.
+  virtual const AccountId& GetUserPresentingWindow(
+      const aura::Window* window) const = 0;
+
+  // Returns the id of the currently active user.
+  virtual const AccountId& CurrentAccountId() const = 0;
+
+ protected:
+  MultiUserWindowManager() {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_MULTI_USER_WINDOW_MANAGER_H_
diff --git a/ash/public/cpp/multi_user_window_manager_delegate.h b/ash/public/cpp/multi_user_window_manager_delegate.h
new file mode 100644
index 0000000..e3f2d06
--- /dev/null
+++ b/ash/public/cpp/multi_user_window_manager_delegate.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_MULTI_USER_WINDOW_MANAGER_DELEGATE_H_
+#define ASH_PUBLIC_CPP_MULTI_USER_WINDOW_MANAGER_DELEGATE_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+
+class AccountId;
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+
+class ASH_PUBLIC_EXPORT MultiUserWindowManagerDelegate {
+ public:
+  // Called when the owner of a window supplied to SetWindowOwner() changes.
+  // |was_minimized| is true if the window was minimized. |teleported| is true
+  // if the window was not on the desktop of the current user.
+  virtual void OnWindowOwnerEntryChanged(aura::Window* window,
+                                         const AccountId& account_id,
+                                         bool was_minimized,
+                                         bool teleported) = 0;
+
+  // Called at the time when the user's shelf should transition to the account
+  // supplied to OnWillSwitchActiveAccount().
+  virtual void OnTransitionUserShelfToNewAccount() = 0;
+
+  // Called when the active account change is complete.
+  virtual void OnDidSwitchActiveAccount() = 0;
+
+ protected:
+  virtual ~MultiUserWindowManagerDelegate() {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_MULTI_USER_WINDOW_MANAGER_DELEGATE_H_
diff --git a/ash/public/interfaces/BUILD.gn b/ash/public/interfaces/BUILD.gn
index 608f881..3065a06f 100644
--- a/ash/public/interfaces/BUILD.gn
+++ b/ash/public/interfaces/BUILD.gn
@@ -49,7 +49,6 @@
     "login_user_info.mojom",
     "media.mojom",
     "menu.mojom",
-    "multi_user_window_manager.mojom",
     "new_window.mojom",
     "night_light_controller.mojom",
     "note_taking_controller.mojom",
diff --git a/ash/public/interfaces/multi_user_window_manager.mojom b/ash/public/interfaces/multi_user_window_manager.mojom
deleted file mode 100644
index 390a386..0000000
--- a/ash/public/interfaces/multi_user_window_manager.mojom
+++ /dev/null
@@ -1,45 +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.
-
-module ash.mojom;
-
-import "components/account_id/interfaces/account_id.mojom";
-
-// Used to assign windows to user accounts so that ash shows the appropriate set
-// of windows based on the active user.
-interface MultiUserWindowManager {
-  SetClient(associated MultiUserWindowManagerClient client);
-
-  // Associates a window with an account. If |show_for_current_user| is true,
-  // the window is associated with |account_id|, but is shown for the currently
-  // active user.
-  SetWindowOwner(uint64 window_id,
-                 signin.mojom.AccountId account_id,
-                 bool show_for_current_user);
-
-  // Shows a previously registered window for the specified account.
-  ShowWindowForUser(uint64 window_id,
-                    signin.mojom.AccountId account_id);
-};
-
-interface MultiUserWindowManagerClient {
-  // Called when the owner of a window supplied to SetWindowOwner() changes.
-  // |was_minimized| is true if the window was minimized. |teleported| is true
-  // if the window was not on the desktop of the current user.
-  OnWindowOwnerEntryChanged(uint64 window_id,
-                            signin.mojom.AccountId account_id,
-                            bool was_minimized,
-                            bool teleported);
-
-  // Called when the active account changes. This is followed by
-  // OnTransitionUserShelfToNewAccount() and OnDidSwitchActiveAccount().
-  OnWillSwitchActiveAccount(signin.mojom.AccountId account_id);
-
-  // Called at the time when the user's shelf should transition to the account
-  // supplied to OnWillSwitchActiveAccount().
-  OnTransitionUserShelfToNewAccount();
-
-  // Called when the active account change is complete.
-  OnDidSwitchActiveAccount();
-};
diff --git a/ash/shelf/app_list_button_unittest.cc b/ash/shelf/app_list_button_unittest.cc
index 4e31a90..2698d5f7 100644
--- a/ash/shelf/app_list_button_unittest.cc
+++ b/ash/shelf/app_list_button_unittest.cc
@@ -30,6 +30,7 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_command_line.h"
+#include "base/test/scoped_feature_list.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/test/event_generator.h"
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 81189c53..2c9232a3 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -641,12 +641,6 @@
   MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
 }
 
-void ShelfLayoutManager::OnAppListToggled() {
-  // Reset the Shelf's drag status to deal with the edge case that toggling
-  // the AppList button while dragging the shelf in auto-hide mode.
-  gesture_drag_status_ = GestureDragStatus::GESTURE_DRAG_NONE;
-}
-
 void ShelfLayoutManager::OnHomeLauncherTargetPositionChanged(
     bool showing,
     int64_t display_id) {
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
index 83d759c..1c72070 100644
--- a/ash/shelf/shelf_layout_manager.h
+++ b/ash/shelf/shelf_layout_manager.h
@@ -141,7 +141,6 @@
 
   // AppListControllerObserver:
   void OnAppListVisibilityChanged(bool shown, int64_t display_id) override;
-  void OnAppListToggled() override;
 
   // HomeLauncherGestureHandlerObserver:
   void OnHomeLauncherTargetPositionChanged(bool showing,
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 007a08a..3aad93f 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -11,7 +11,6 @@
 #include "ash/accelerators/accelerator_table.h"
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
-#include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/focus_cycler.h"
@@ -23,7 +22,6 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/root_window_controller.h"
-#include "ash/screen_util.h"
 #include "ash/session/session_controller.h"
 #include "ash/shelf/app_list_button.h"
 #include "ash/shelf/shelf.h"
@@ -84,12 +82,6 @@
 namespace ash {
 namespace {
 
-void PressAppListButton() {
-  ash::Shell::Get()->app_list_controller()->OnAppListButtonPressed(
-      display::Screen::GetScreen()->GetPrimaryDisplay().id(),
-      app_list::AppListShowSource::kShelfButton, base::TimeTicks());
-}
-
 void StepWidgetLayerAnimatorToEnd(views::Widget* widget) {
   widget->GetNativeView()->layer()->GetAnimator()->Step(
       base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
@@ -2540,59 +2532,6 @@
   TestHomeLauncherGestureHandler(/*autohide_shelf=*/true);
 }
 
-// Tests that the auto-hide shelf has expected behavior when pressing the
-// AppList button while the shelf is being dragged by gesture (see
-// https://crbug.com/953877).
-TEST_F(ShelfLayoutManagerTest, PressAppListBtnWhenShelfBeingDragged) {
-  // Create a widget to hide the shelf in auto-hide mode.
-  CreateTestWidget();
-  GetPrimaryShelf()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
-  EXPECT_FALSE(GetPrimaryShelf()->IsVisible());
-
-  const WorkAreaInsets* const work_area =
-      WorkAreaInsets::ForWindow(GetShelfWidget()->GetNativeWindow());
-  gfx::Rect available_bounds = screen_util::GetDisplayBoundsWithShelf(
-      GetShelfWidget()->GetNativeWindow());
-  available_bounds.Inset(work_area->GetAccessibilityInsets());
-
-  // Emulate to drag the shelf to show it.
-  gfx::Point gesture_location = display::Screen::GetScreen()
-                                    ->GetPrimaryDisplay()
-                                    .bounds()
-                                    .bottom_center();
-  int delta_y = -1;
-  base::TimeTicks timestamp = base::TimeTicks::Now();
-
-  ui::GestureEvent start_event = ui::GestureEvent(
-      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
-      ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, delta_y));
-  GetPrimaryShelf()->ProcessGestureEvent(start_event);
-  gesture_location.Offset(0, delta_y);
-  delta_y = -20;
-  timestamp += base::TimeDelta::FromMilliseconds(200);
-  ui::GestureEvent update_event = ui::GestureEvent(
-      gesture_location.x(), gesture_location.y(), ui::EF_NONE, timestamp,
-      ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, delta_y));
-  GetPrimaryShelf()->ProcessGestureEvent(update_event);
-
-  // Emulate to press the AppList button while dragging the Shelf.
-  PressAppListButton();
-  EXPECT_TRUE(GetPrimaryShelf()->IsVisible());
-
-  // Press the AppList button to hide the AppList and Shelf. Check the following
-  // things:
-  // (1) Shelf is hidden
-  // (2) Shelf has correct bounds in screen coordinate.
-  PressAppListButton();
-  EXPECT_EQ(available_bounds.bottom_left() +
-                gfx::Point(0, -kHiddenShelfInScreenPortion).OffsetFromOrigin(),
-            GetPrimaryShelf()
-                ->GetShelfViewForTesting()
-                ->GetBoundsInScreen()
-                .origin());
-  EXPECT_FALSE(GetPrimaryShelf()->IsVisible());
-}
-
 // Tests that tap outside of the AUTO_HIDE_SHOWN shelf should hide it.
 TEST_F(ShelfLayoutManagerTest, TapOutsideOfAutoHideShownShelf) {
   views::Widget* widget = CreateTestWidget();
diff --git a/ash/shell_test_api.cc b/ash/shell_test_api.cc
index fbe3d4d..bab2bfc9 100644
--- a/ash/shell_test_api.cc
+++ b/ash/shell_test_api.cc
@@ -200,11 +200,8 @@
 }
 
 void ShellTestApi::EnableVirtualKeyboard(EnableVirtualKeyboardCallback cb) {
-  // TODO(https://crbug.com/845780): The callers to this function have already
-  // enabled the virtual keyboard. For some reason, in those tests, the virtual
-  // keyboard requires a rebuild. Remove this function once we no longer need a
-  // rebuild.
-  shell_->ash_keyboard_controller()->RebuildKeyboardIfEnabled();
+  shell_->ash_keyboard_controller()->SetEnableFlag(
+      keyboard::mojom::KeyboardEnableFlag::kCommandLineEnabled);
   std::move(cb).Run();
 }
 
diff --git a/ash/system/message_center/unified_message_center_view_unittest.cc b/ash/system/message_center/unified_message_center_view_unittest.cc
index 8421fb23..acf04b80 100644
--- a/ash/system/message_center/unified_message_center_view_unittest.cc
+++ b/ash/system/message_center/unified_message_center_view_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/prefs/pref_service.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/views/message_view.h"
diff --git a/ash/system/message_center/unified_message_list_view_unittest.cc b/ash/system/message_center/unified_message_list_view_unittest.cc
index ee8d8a3..b7d43675 100644
--- a/ash/system/message_center/unified_message_list_view_unittest.cc
+++ b/ash/system/message_center/unified_message_list_view_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/test/ash_test_base.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/views/message_view.h"
 #include "ui/message_center/views/notification_view_md.h"
diff --git a/ash/system/palette/palette_tray_unittest.cc b/ash/system/palette/palette_tray_unittest.cc
index 3ecc10c..7361769 100644
--- a/ash/system/palette/palette_tray_unittest.cc
+++ b/ash/system/palette/palette_tray_unittest.cc
@@ -32,6 +32,7 @@
 #include "base/command_line.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "components/prefs/pref_service.h"
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index 74231ddd..6e93ed4 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -56,14 +56,12 @@
 #include "ui/aura/env.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/aura/test/aura_test_utils.h"
-#include "ui/aura/test/env_test_helper.h"
 #include "ui/aura/test/event_generator_delegate_aura.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/ime/init/input_method_initializer.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
@@ -561,11 +559,6 @@
   return window_tree_test_helper_.get();
 }
 
-ws::TestWindowTreeClient* AshTestBase::GetTestWindowTreeClient() {
-  CreateWindowTreeIfNecessary();
-  return window_tree_client_.get();
-}
-
 ws::WindowTree* AshTestBase::GetWindowTree() {
   CreateWindowTreeIfNecessary();
   return window_tree_.get();
@@ -585,32 +578,4 @@
       std::make_unique<ws::WindowTreeTestHelper>(window_tree_.get());
 }
 
-SingleProcessMashTestBase::SingleProcessMashTestBase() = default;
-
-SingleProcessMashTestBase::~SingleProcessMashTestBase() = default;
-
-void SingleProcessMashTestBase::SetUp() {
-  original_aura_env_mode_ =
-      aura::test::EnvTestHelper().SetMode(aura::Env::Mode::MUS);
-  feature_list_.InitWithFeatures({::features::kSingleProcessMash}, {});
-  AshTestBase::SetUp();
-
-  // TabletModeController calls to PowerManagerClient with a callback that is
-  // run via a posted task. Run the loop now so that we know the task is
-  // processed. Without this, the task gets processed later on, which may
-  // interfer with things.
-  base::RunLoop().RunUntilIdle();
-
-  // This test configures views with mus, which means it triggers some of the
-  // DCHECKs ensuring Shell's Env is used.
-  SetRunningOutsideAsh();
-
-  ash_test_helper()->CreateMusClient();
-}
-
-void SingleProcessMashTestBase::TearDown() {
-  AshTestBase::TearDown();
-  aura::test::EnvTestHelper().SetMode(original_aura_env_mode_);
-}
-
 }  // namespace ash
diff --git a/ash/test/ash_test_base.h b/ash/test/ash_test_base.h
index c36616dc..dfe3872 100644
--- a/ash/test/ash_test_base.h
+++ b/ash/test/ash_test_base.h
@@ -15,7 +15,6 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/wm/desks/desks_util.h"
 #include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread.h"
 #include "components/user_manager/user_type.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -271,7 +270,6 @@
 
   // Returns the WindowTreeTestHelper, creating if necessary.
   ws::WindowTreeTestHelper* GetWindowTreeTestHelper();
-  ws::TestWindowTreeClient* GetTestWindowTreeClient();
   ws::WindowTree* GetWindowTree();
 
  private:
@@ -304,26 +302,6 @@
   DISALLOW_COPY_AND_ASSIGN(NoSessionAshTestBase);
 };
 
-// Base test class that forces single-process mash to be enabled *and* creates
-// a views::MusClient. This base class is useful for testing WindowService
-// related functionality exposed by Ash.
-// TODO(sky): this name is misleading. Rename to better indicate what it does.
-class SingleProcessMashTestBase : public AshTestBase {
- public:
-  SingleProcessMashTestBase();
-  ~SingleProcessMashTestBase() override;
-
-  // AshTestBase:
-  void SetUp() override;
-  void TearDown() override;
-
- private:
-  aura::Env::Mode original_aura_env_mode_ = aura::Env::Mode::LOCAL;
-  base::test::ScopedFeatureList feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(SingleProcessMashTestBase);
-};
-
 }  // namespace ash
 
 #endif  // ASH_TEST_ASH_TEST_BASE_H_
diff --git a/ash/utility/screenshot_controller_unittest.cc b/ash/utility/screenshot_controller_unittest.cc
index 97aff2ef..cf0a702 100644
--- a/ash/utility/screenshot_controller_unittest.cc
+++ b/ash/utility/screenshot_controller_unittest.cc
@@ -480,20 +480,4 @@
   EXPECT_FALSE(window->HasCapture());
 }
 
-TEST_F(ScreenshotControllerTest, DontTargetNonTopLevels) {
-  std::unique_ptr<aura::Window> toplevel = CreateTestWindow();
-  std::unique_ptr<aura::Window> content(GetWindowTreeTestHelper()->NewWindow());
-  content->SetBounds(gfx::Rect(toplevel->bounds().size()));
-  toplevel->AddChild(content.get());
-  content->set_owned_by_parent(false);
-  content->Show();
-
-  StartWindowScreenshotSession();
-
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
-  generator.MoveMouseTo(toplevel->GetBoundsInScreen().CenterPoint());
-
-  EXPECT_EQ(toplevel.get(), GetCurrentSelectedWindow());
-}
-
 }  // namespace ash
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index aff481d2..25ceea104 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -84,8 +84,12 @@
 
   for (auto* transient_window : wm::GetTransientTreeIterator(window)) {
     const auto result = windows_.emplace(transient_window);
-    DCHECK(result.second);
-    transient_window->AddObserver(this);
+
+    // GetTransientTreeIterator() starts iterating the transient tree hierarchy
+    // from the root transient parent, which may include windows that we already
+    // track from before.
+    if (result.second)
+      transient_window->AddObserver(this);
   }
 }
 
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index c7ea3a4c..a77b83a 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -7,6 +7,7 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/window_factory.h"
 #include "ash/wm/desks/close_desk_button.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desk_mini_view.h"
@@ -20,6 +21,7 @@
 #include "ash/wm/window_util.h"
 #include "base/stl_util.h"
 #include "base/test/scoped_feature_list.h"
+#include "ui/aura/client/window_parenting_client.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/wm/core/window_util.h"
 
@@ -27,6 +29,20 @@
 
 namespace {
 
+std::unique_ptr<aura::Window> CreateTransientWindow(
+    aura::Window* transient_parent,
+    const gfx::Rect& bounds) {
+  std::unique_ptr<aura::Window> window =
+      window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_POPUP);
+  window->Init(ui::LAYER_NOT_DRAWN);
+  window->SetBounds(bounds);
+  ::wm::AddTransientChild(transient_parent, window.get());
+  aura::client::ParentWindowWithContext(
+      window.get(), transient_parent->GetRootWindow(), bounds);
+  window->Show();
+  return window;
+}
+
 bool DoesActiveDeskContainWindow(aura::Window* window) {
   return DesksController::Get()->active_desk()->windows().contains(window);
 }
@@ -283,9 +299,7 @@
 
   // Create two windows, one is a transient child of the other.
   auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
-  auto win1 = CreateTestWindow(gfx::Rect(100, 100, 100, 100),
-                               aura::client::WINDOW_TYPE_POPUP);
-  ::wm::AddTransientChild(win0.get(), win1.get());
+  auto win1 = CreateTransientWindow(win0.get(), gfx::Rect(100, 100, 100, 100));
 
   EXPECT_EQ(2u, desk_1->windows().size());
   EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get()));
@@ -305,14 +319,23 @@
   EXPECT_FALSE(desk_1->is_active());
   EXPECT_TRUE(desk_2->is_active());
 
+  // Create another transient child of the earlier transient child, and confirm
+  // it's tracked in desk_1 (even though desk_2 is the currently active one).
+  // This is because the transient parent exists in desk_1.
+  auto win2 = CreateTransientWindow(win1.get(), gfx::Rect(100, 100, 50, 50));
+  ::wm::AddTransientChild(win1.get(), win2.get());
+  EXPECT_EQ(3u, desk_1->windows().size());
+  EXPECT_FALSE(DoesActiveDeskContainWindow(win2.get()));
+
   // Remove the inactive desk 1, and expect that its windows, including
   // transient will move to desk 2.
   controller->RemoveDesk(desk_1);
   EXPECT_EQ(1u, controller->desks().size());
   EXPECT_EQ(desk_2, controller->active_desk());
-  EXPECT_EQ(2u, desk_2->windows().size());
+  EXPECT_EQ(3u, desk_2->windows().size());
   EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get()));
   EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get()));
+  EXPECT_TRUE(DoesActiveDeskContainWindow(win2.get()));
 }
 
 TEST_F(DesksTest, WindowActivation) {
diff --git a/ash/wm/non_client_frame_controller_unittest.cc b/ash/wm/non_client_frame_controller_unittest.cc
deleted file mode 100644
index 7cfa5a9..0000000
--- a/ash/wm/non_client_frame_controller_unittest.cc
+++ /dev/null
@@ -1,199 +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/non_client_frame_controller.h"
-
-#include <vector>
-
-#include "ash/test/ash_test_base.h"
-#include "ash/wm/top_level_window_factory.h"
-#include "base/strings/utf_string_conversions.h"
-#include "mojo/public/cpp/bindings/map.h"
-#include "mojo/public/cpp/bindings/type_converter.h"
-#include "services/ws/public/cpp/property_type_converters.h"
-#include "services/ws/public/mojom/window_manager.mojom.h"
-#include "services/ws/test_change_tracker.h"
-#include "services/ws/test_window_tree_client.h"
-#include "services/ws/window_tree_test_helper.h"
-#include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/accessibility/platform/aura_window_properties.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/env.h"
-#include "ui/aura/test/mus/change_completion_waiter.h"
-#include "ui/aura/window.h"
-#include "ui/aura/window_delegate.h"
-#include "ui/base/hit_test.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/views/mus/mus_client.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-using NonClientFrameControllerTest = AshTestBase;
-
-TEST_F(NonClientFrameControllerTest, CallsRequestClose) {
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  NonClientFrameController* non_client_frame_controller =
-      NonClientFrameController::Get(window.get());
-  ASSERT_TRUE(non_client_frame_controller);
-  non_client_frame_controller->GetWidget()->Close();
-  // Close should not have been scheduled on the widget yet (because the request
-  // goes to the remote client).
-  EXPECT_FALSE(non_client_frame_controller->GetWidget()->IsClosed());
-  auto* changes = GetTestWindowTreeClient()->tracker()->changes();
-  ASSERT_FALSE(changes->empty());
-  // The remote client should have a request to close the window.
-  EXPECT_EQ("RequestClose", ws::ChangeToDescription(changes->back()));
-}
-
-TEST_F(NonClientFrameControllerTest, WindowTitle) {
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  NonClientFrameController* non_client_frame_controller =
-      NonClientFrameController::Get(window.get());
-  ASSERT_TRUE(non_client_frame_controller);
-  EXPECT_TRUE(non_client_frame_controller->ShouldShowWindowTitle());
-  EXPECT_TRUE(non_client_frame_controller->GetWindowTitle().empty());
-
-  // Verify GetWindowTitle() mirrors window->SetTitle().
-  const base::string16 title = base::ASCIIToUTF16("X");
-  window->SetTitle(title);
-  EXPECT_EQ(title, non_client_frame_controller->GetWindowTitle());
-
-  // ShouldShowWindowTitle() mirrors |aura::client::kTitleShownKey|.
-  window->SetProperty(aura::client::kTitleShownKey, false);
-  EXPECT_FALSE(non_client_frame_controller->ShouldShowWindowTitle());
-}
-
-TEST_F(NonClientFrameControllerTest, ExposesChildTreeIdToAccessibility) {
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  const std::string ax_tree_id = "123";
-  window->SetProperty(ui::kChildAXTreeID, new std::string(ax_tree_id));
-  NonClientFrameController* non_client_frame_controller =
-      NonClientFrameController::Get(window.get());
-  views::View* contents_view = non_client_frame_controller->GetContentsView();
-  ui::AXNodeData ax_node_data;
-  contents_view->GetAccessibleNodeData(&ax_node_data);
-  EXPECT_EQ(ax_tree_id, ax_node_data.GetStringAttribute(
-                            ax::mojom::StringAttribute::kChildTreeId));
-  EXPECT_EQ(ax::mojom::Role::kClient, ax_node_data.role);
-}
-
-TEST_F(NonClientFrameControllerTest, HonorsMinimumSize) {
-  const gfx::Size min_size(201, 302);
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  // |window| takes ownership of the new size.
-  window->SetProperty(aura::client::kMinimumSize, new gfx::Size(min_size));
-  ASSERT_TRUE(window->delegate());
-  EXPECT_EQ(min_size, window->delegate()->GetMinimumSize());
-}
-
-TEST_F(NonClientFrameControllerTest, HonorsMinimumSizeWithoutFrame) {
-  // Variant of HonorsMinimumSize that removes the standard frame (the client
-  // draws the non-client area).
-  using TransportType = std::vector<uint8_t>;
-  const gfx::Size min_size(201, 302);
-  auto properties = CreatePropertiesForProxyWindow();
-  properties[ws::mojom::WindowManager::kMinimumSize_Property] =
-      mojo::ConvertTo<TransportType>(min_size);
-  properties[ws::mojom::WindowManager::kClientProvidesFrame_InitProperty] =
-      mojo::ConvertTo<TransportType>(true);
-  std::unique_ptr<aura::Window> window(
-      GetWindowTreeTestHelper()->NewTopLevelWindow(
-          mojo::MapToFlatMap(properties)));
-  ASSERT_TRUE(window->delegate());
-  EXPECT_EQ(min_size, window->delegate()->GetMinimumSize());
-}
-
-TEST_F(NonClientFrameControllerTest, NonClientAreaShouldBeDraggable) {
-  using TransportType = std::vector<uint8_t>;
-  auto properties = CreatePropertiesForProxyWindow();
-  properties[ws::mojom::WindowManager::kClientProvidesFrame_InitProperty] =
-      mojo::ConvertTo<TransportType>(true);
-  std::unique_ptr<aura::Window> window(
-      GetWindowTreeTestHelper()->NewTopLevelWindow(
-          mojo::MapToFlatMap(properties)));
-
-  const gfx::Point point(10, 10);
-  EXPECT_EQ(HTCLIENT, window->delegate()->GetNonClientComponent(point));
-  EXPECT_EQ(HTTOPLEFT,
-            window->delegate()->GetNonClientComponent(gfx::Point(-1, -1)));
-
-  std::vector<gfx::Rect> additional_areas = {
-      gfx::Rect(window->bounds().width() - 20, 0, 20, 20)};
-  GetWindowTreeTestHelper()->SetClientArea(
-      window.get(), gfx::Insets(20, 20, 20, 20), additional_areas);
-  EXPECT_EQ(HTCAPTION, window->delegate()->GetNonClientComponent(point));
-  EXPECT_EQ(HTTOPLEFT,
-            window->delegate()->GetNonClientComponent(gfx::Point(-1, -1)));
-  EXPECT_EQ(HTCLIENT,
-            window->delegate()->GetNonClientComponent(gfx::Point(30, 30)));
-  EXPECT_EQ(HTCLIENT, window->delegate()->GetNonClientComponent(
-                          gfx::Point(window->bounds().width() - 10, 10)));
-}
-
-using NonClientFrameControllerSingleProcessMashTest = SingleProcessMashTestBase;
-
-// Used to track whether in a window resize loop.
-class ResizeLoopWidgetDelegate : public views::WidgetDelegateView {
- public:
-  ResizeLoopWidgetDelegate() = default;
-  ~ResizeLoopWidgetDelegate() override = default;
-
-  bool in_resize_loop() const { return in_resize_loop_; }
-
-  // views::WidgetDelegateView:
-  void OnWindowBeginUserBoundsChange() override {
-    EXPECT_FALSE(in_resize_loop_);
-    in_resize_loop_ = true;
-  }
-  void OnWindowEndUserBoundsChange() override {
-    EXPECT_TRUE(in_resize_loop_);
-    in_resize_loop_ = false;
-  }
-  int32_t GetResizeBehavior() const override {
-    return ws::mojom::kResizeBehaviorCanResize;
-  }
-
- private:
-  bool in_resize_loop_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(ResizeLoopWidgetDelegate);
-};
-
-TEST_F(NonClientFrameControllerSingleProcessMashTest, ResizeLoop) {
-  // Owned by |widget|.
-  ResizeLoopWidgetDelegate* widget_delegate = new ResizeLoopWidgetDelegate;
-  // Create a widget. This widget is backed by mus.
-  views::Widget widget;
-  views::Widget::InitParams params;
-  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  params.bounds = gfx::Rect(0, 0, 200, 200);
-  params.delegate = widget_delegate;
-  params.native_widget =
-      views::MusClient::Get()->CreateNativeWidget(params, &widget);
-  widget.Init(params);
-  widget.Show();
-
-  // Should not initially be in a resize loop.
-  EXPECT_FALSE(widget_delegate->in_resize_loop());
-
-  // Flush all messages from the WindowTreeClient to ensure ash processes the
-  // widget creation.
-  aura::test::WaitForAllChangesToComplete();
-
-  // The resize loop is entered once a possible resize is detected.
-  GetEventGenerator()->MoveMouseTo(gfx::Point(5, 199));
-  GetEventGenerator()->PressLeftButton();
-  aura::test::WaitForAllChangesToComplete();
-  EXPECT_TRUE(widget_delegate->in_resize_loop());
-
-  // Releasing the button ends the loop.
-  GetEventGenerator()->ReleaseLeftButton();
-  aura::test::WaitForAllChangesToComplete();
-  EXPECT_FALSE(widget_delegate->in_resize_loop());
-}
-
-}  // namespace ash
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index c19bf7bc..7ef7baab 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1232,7 +1232,6 @@
 OverviewGrid::UpdateYPositionAndOpacity(
     int new_y,
     float opacity,
-    const gfx::Rect& work_area,
     OverviewSession::UpdateAnimationSettingsCallback callback) {
   DCHECK(!window_list_.empty());
   // Translate the window items to |new_y| with the opacity. Observe the
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index e2957a3e..6d753378d 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -240,7 +240,6 @@
   std::unique_ptr<ui::ScopedLayerAnimationSettings> UpdateYPositionAndOpacity(
       int new_y,
       float opacity,
-      const gfx::Rect& work_area,
       OverviewSession::UpdateAnimationSettingsCallback callback);
 
   // Returns the window of the overview item that contains |location_in_screen|.
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 7fbe025..77e834e 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -46,6 +46,7 @@
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/core/shadow_types.h"
+#include "ui/wm/core/window_util.h"
 
 namespace ash {
 
@@ -326,33 +327,10 @@
     int new_grid_y,
     float opacity,
     OverviewSession::UpdateAnimationSettingsCallback callback) {
-  // Animate |item_widget_| and the window itself.
-  // TODO(sammiequon): Investigate if we can combine with
-  // FadeInWidgetAndMaybeSlideOnEnter. Also when animating we should remove
-  // shadow and rounded corners.
-  std::vector<std::pair<ui::Layer*, int>> animation_layers_and_offsets = {
-      {item_widget_->GetNativeWindow()->layer(), 0}};
-
-  // For minimized windows we don't need to animate the original window or its
-  // transient ancestors since they are not visible.
-  if (!transform_window_.IsMinimized()) {
-    // Transient children may already have a y translation relative to their
-    // base ancestor, so factor that in when computing their new y translation.
-    base::Optional<int> base_window_y_translation = base::nullopt;
-    for (auto* window : wm::GetTransientTreeIterator(GetWindow())) {
-      if (!base_window_y_translation.has_value()) {
-        base_window_y_translation = base::make_optional(
-            window->layer()->transform().To2dTranslation().y());
-      }
-      const int offset = *base_window_y_translation -
-                         window->layer()->transform().To2dTranslation().y();
-      animation_layers_and_offsets.push_back({window->layer(), offset});
-    }
-  }
-
+  aura::Window::Windows windows = GetWindowsForHomeGesture();
   std::unique_ptr<ui::ScopedLayerAnimationSettings> settings_to_observe;
-  for (auto& layer_and_offset : animation_layers_and_offsets) {
-    ui::Layer* layer = layer_and_offset.first;
+  for (auto* window : windows) {
+    ui::Layer* layer = window->layer();
     std::unique_ptr<ui::ScopedLayerAnimationSettings> settings;
     if (!callback.is_null()) {
       settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
@@ -361,12 +339,15 @@
     }
     layer->SetOpacity(opacity);
 
+    int initial_y_ = 0;
+    if (translation_y_map_.contains(window))
+      initial_y_ = translation_y_map_[window];
+
     // Alter the y-translation. Offset by the window location relative to the
     // grid.
-    const int offset = target_bounds_.y() + kHeaderHeightDp + kWindowMargin -
-                       layer_and_offset.second;
     gfx::Transform transform = layer->transform();
-    transform.matrix().setFloat(1, 3, static_cast<float>(offset + new_grid_y));
+    transform.matrix().setFloat(1, 3,
+                                static_cast<float>(initial_y_ - new_grid_y));
     layer->SetTransform(transform);
 
     // Return the first layer for the caller to observe.
@@ -473,6 +454,22 @@
             gfx::ToEnclosingRect(inset_bounds)),
         new_animation_type, nullptr);
   }
+
+  translation_y_map_.clear();
+  aura::Window::Windows windows = GetWindowsForHomeGesture();
+  for (auto* window : windows) {
+    // There is a bug where the first OverviewItem |window_| will always return
+    // the identity, even though a transform has been visually applied. For this
+    // case use the y location of the screen bounds.
+    // TODO: Investigate why this is happening and remove the if clause.
+    if (window->transform().IsIdentity() &&
+        (window == GetWindow() ||
+         ::wm::HasTransientAncestor(window, GetWindow()))) {
+      translation_y_map_[window] = window->GetBoundsInScreen().y();
+    } else {
+      translation_y_map_[window] = window->transform().To2dTranslation().y();
+    }
+  }
 }
 
 void OverviewItem::SendAccessibleSelectionEvent() {
@@ -1085,4 +1082,15 @@
   }
 }
 
+aura::Window::Windows OverviewItem::GetWindowsForHomeGesture() {
+  aura::Window::Windows windows = {item_widget_->GetNativeWindow()};
+  if (!transform_window_.IsMinimized()) {
+    for (auto* window : wm::GetTransientTreeIterator(GetWindow()))
+      windows.push_back(window);
+  }
+  if (cannot_snap_widget_)
+    windows.push_back(cannot_snap_widget_->GetNativeWindow());
+  return windows;
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h
index 6767aa8..324e5df 100644
--- a/ash/wm/overview/overview_item.h
+++ b/ash/wm/overview/overview_item.h
@@ -11,6 +11,7 @@
 #include "ash/wm/overview/caption_container_view.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/scoped_overview_transform_window.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "ui/aura/window_observer.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -273,6 +274,10 @@
   // it visible while dragging around.
   void StartDrag();
 
+  // Returns the list of windows that we want to slide up or down when swiping
+  // on the shelf in tablet mode.
+  aura::Window::Windows GetWindowsForHomeGesture();
+
   // The root window this item is being displayed on.
   aura::Window* root_window_;
 
@@ -335,6 +340,11 @@
   // True to always disable mask regardless of the state.
   bool disable_mask_ = false;
 
+  // Stores the last translations of the windows affected by SetBounds. Used for
+  // ease of calculations when swiping away overview mode using home launcher
+  // gesture.
+  base::flat_map<aura::Window*, int> translation_y_map_;
+
   // The shadow around the overview window. Shadows the original window, not
   // |item_widget_|. Done here instead of on the original window because of the
   // rounded edges mask applied on entering overview window.
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 07fbe4e..daf2614 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -654,7 +654,6 @@
     int64_t display_id,
     int new_y,
     float opacity,
-    const gfx::Rect& work_area,
     UpdateAnimationSettingsCallback callback) {
   OverviewGrid* grid = GetGridWithRootWindow(
       Shell::Get()->GetRootWindowForDisplayId(display_id));
@@ -677,7 +676,7 @@
     return settings;
   }
 
-  return grid->UpdateYPositionAndOpacity(new_y, opacity, work_area, callback);
+  return grid->UpdateYPositionAndOpacity(new_y, opacity, callback);
 }
 
 void OverviewSession::UpdateMaskAndShadow() {
diff --git a/ash/wm/overview/overview_session.h b/ash/wm/overview/overview_session.h
index 4bbff87..3936b0d 100644
--- a/ash/wm/overview/overview_session.h
+++ b/ash/wm/overview/overview_session.h
@@ -25,7 +25,6 @@
 
 namespace gfx {
 class Point;
-class Rect;
 }  // namespace gfx
 
 namespace ui {
@@ -221,7 +220,6 @@
       int64_t display_id,
       int new_y,
       float opacity,
-      const gfx::Rect& work_area,
       UpdateAnimationSettingsCallback callback);
 
   // Updates all the overview items' mask and shadow.
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index 6ebf24ce..b552448 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -231,8 +231,12 @@
   if (Shell::Get()->shadow_controller())
     Shell::Get()->shadow_controller()->UpdateShadowForWindow(window_);
 
-  if (IsMinimized())
+  if (IsMinimized()) {
+    // Minimized windows may have had their transforms altered by swiping up
+    // from the shelf.
+    SetTransform(window_, gfx::Transform());
     return;
+  }
 
   if (reset_transform) {
     ScopedAnimationSettings animation_settings_list;
diff --git a/ash/wm/top_level_window_factory_unittest.cc b/ash/wm/top_level_window_factory_unittest.cc
deleted file mode 100644
index 432b77b..0000000
--- a/ash/wm/top_level_window_factory_unittest.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2016 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/top_level_window_factory.h"
-
-#include <stdint.h>
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/test/ash_test_helper.h"
-#include "ash/wm/desks/desks_util.h"
-#include "ash/wm/window_properties.h"
-#include "mojo/public/cpp/bindings/map.h"
-#include "services/ws/public/cpp/property_type_converters.h"
-#include "services/ws/public/mojom/window_manager.mojom.h"
-#include "services/ws/window_tree_test_helper.h"
-#include "ui/aura/mus/property_converter.h"
-#include "ui/aura/window.h"
-#include "ui/display/screen.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/test/gfx_util.h"
-#include "ui/wm/core/window_util.h"
-
-namespace ash {
-
-namespace {
-
-int64_t GetDisplayId(aura::Window* window) {
-  return display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
-}
-
-}  // namespace
-
-class TopLevelWindowFactoryTest : public AshTestBase {
- public:
-  TopLevelWindowFactoryTest() = default;
-  ~TopLevelWindowFactoryTest() override = default;
-
-  aura::Window* CreateFullscreenTestWindow(int64_t display_id) {
-    std::map<std::string, std::vector<uint8_t>> properties;
-    properties[ws::mojom::WindowManager::kShowState_Property] =
-        mojo::ConvertTo<std::vector<uint8_t>>(
-            static_cast<aura::PropertyConverter::PrimitiveType>(
-                ws::mojom::ShowState::FULLSCREEN));
-    if (display_id != display::kInvalidDisplayId) {
-      properties[ws::mojom::WindowManager::kDisplayId_InitProperty] =
-          mojo::ConvertTo<std::vector<uint8_t>>(display_id);
-    }
-    properties[ws::mojom::WindowManager::kWindowType_InitProperty] =
-        mojo::ConvertTo<std::vector<uint8_t>>(
-            static_cast<int32_t>(ws::mojom::WindowType::WINDOW));
-    aura::Window* window = GetWindowTreeTestHelper()->NewTopLevelWindow(
-        mojo::MapToFlatMap(std::move(properties)));
-    window->Show();
-    return window;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TopLevelWindowFactoryTest);
-};
-
-TEST_F(TopLevelWindowFactoryTest, CreateFullscreenWindow) {
-  std::unique_ptr<aura::Window> window = CreateTestWindow();
-  ::wm::SetWindowFullscreen(window.get(), true);
-  aura::Window* root_window = Shell::GetPrimaryRootWindow();
-  EXPECT_EQ(root_window->bounds(), window->bounds());
-}
-
-TEST_F(TopLevelWindowFactoryTest, IsWindowShownInCorrectDisplay) {
-  UpdateDisplay("400x400,400x400");
-  EXPECT_NE(GetPrimaryDisplay().id(), GetSecondaryDisplay().id());
-
-  std::unique_ptr<aura::Window> window_primary_display(
-      CreateFullscreenTestWindow(GetPrimaryDisplay().id()));
-  std::unique_ptr<aura::Window> window_secondary_display(
-      CreateFullscreenTestWindow(GetSecondaryDisplay().id()));
-
-  EXPECT_EQ(GetPrimaryDisplay().id(),
-            GetDisplayId(window_primary_display.get()));
-  EXPECT_EQ(GetSecondaryDisplay().id(),
-            GetDisplayId(window_secondary_display.get()));
-}
-
-TEST_F(TopLevelWindowFactoryTest, UnknownWindowTypeReturnsNull) {
-  EXPECT_FALSE(GetWindowTreeTestHelper()->NewTopLevelWindow());
-}
-
-TEST_F(TopLevelWindowFactoryTest, CreateTopLevelWindow) {
-  const gfx::Rect bounds(1, 2, 124, 345);
-  std::map<std::string, std::vector<uint8_t>> properties;
-  properties[ws::mojom::WindowManager::kBounds_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(bounds);
-  properties[ws::mojom::WindowManager::kResizeBehavior_Property] =
-      mojo::ConvertTo<std::vector<uint8_t>>(
-          static_cast<aura::PropertyConverter::PrimitiveType>(
-              ws::mojom::kResizeBehaviorCanResize |
-              ws::mojom::kResizeBehaviorCanMaximize |
-              ws::mojom::kResizeBehaviorCanMinimize));
-  properties[ws::mojom::WindowManager::kWindowType_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(
-          static_cast<int32_t>(ws::mojom::WindowType::WINDOW));
-  aura::Window* window = GetWindowTreeTestHelper()->NewTopLevelWindow(
-      mojo::MapToFlatMap(std::move(properties)));
-  ASSERT_TRUE(window->parent());
-  EXPECT_EQ(desks_util::GetActiveDeskContainerId(), window->parent()->id());
-  EXPECT_EQ(bounds, window->bounds());
-  EXPECT_FALSE(window->IsVisible());
-}
-
-TEST_F(TopLevelWindowFactoryTest, CreateUnfocusableTopLevelWindow) {
-  std::map<std::string, std::vector<uint8_t>> properties;
-  properties[ws::mojom::WindowManager::kFocusable_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(false);
-  properties[ws::mojom::WindowManager::kWindowType_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(
-          static_cast<int32_t>(ws::mojom::WindowType::WINDOW));
-  aura::Window* window = GetWindowTreeTestHelper()->NewTopLevelWindow(
-      mojo::MapToFlatMap(std::move(properties)));
-  ASSERT_TRUE(window);
-  window->Show();
-  // The window should not be focusable as kFocusable_InitProperty was supplied
-  // with a value of false.
-  EXPECT_FALSE(window->CanFocus());
-}
-
-TEST_F(TopLevelWindowFactoryTest, CreateUnfocusablePopupWindow) {
-  std::map<std::string, std::vector<uint8_t>> properties;
-  properties[ws::mojom::WindowManager::kFocusable_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(false);
-  properties[ws::mojom::WindowManager::kWindowType_InitProperty] =
-      mojo::ConvertTo<std::vector<uint8_t>>(
-          static_cast<int32_t>(ws::mojom::WindowType::POPUP));
-  aura::Window* window = GetWindowTreeTestHelper()->NewTopLevelWindow(
-      mojo::MapToFlatMap(std::move(properties)));
-  ASSERT_TRUE(window);
-  window->Show();
-  // The window should not be focusable as kFocusable_InitProperty was supplied
-  // with a value of false.
-  EXPECT_FALSE(window->CanFocus());
-}
-
-}  // namespace ash
diff --git a/ash/wm/window_mirror_view_unittest.cc b/ash/wm/window_mirror_view_unittest.cc
index 968c98c..ddd7340 100644
--- a/ash/wm/window_mirror_view_unittest.cc
+++ b/ash/wm/window_mirror_view_unittest.cc
@@ -5,11 +5,6 @@
 #include "ash/wm/window_mirror_view.h"
 
 #include "ash/test/ash_test_base.h"
-#include "mojo/public/cpp/bindings/map.h"
-#include "services/ws/client_root.h"
-#include "services/ws/client_root_test_helper.h"
-#include "services/ws/proxy_window.h"
-#include "services/ws/window_tree_test_helper.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window_occlusion_tracker.h"
 #include "ui/gfx/geometry/size.h"
@@ -22,74 +17,6 @@
 
 using WindowMirrorViewTest = AshTestBase;
 
-// Regression test for blank Alt-Tab preview windows. https://crbug.com/921224
-TEST_F(WindowMirrorViewTest, RemoteClientWindowHasNonEmptyMirror) {
-  // Simulate the proxy window created by a remote client using the window
-  // service API for a window without a kTopViewInset.
-  auto properties = CreatePropertiesForProxyWindow(gfx::Rect(0, 0, 400, 300));
-  std::unique_ptr<aura::Window> window(
-      GetWindowTreeTestHelper()->NewTopLevelWindow(
-          mojo::MapToFlatMap(properties)));
-  window->SetProperty(aura::client::kTopViewInset, 0);
-
-  // Verify that the mirror view has non-empty bounds.
-  auto mirror_view = std::make_unique<WindowMirrorView>(
-      window.get(), /*trilinear_filtering_on_init=*/false);
-  EXPECT_FALSE(mirror_view->CalculatePreferredSize().IsEmpty());
-}
-
-TEST_F(WindowMirrorViewTest, RemoteClientForcedVisible) {
-  auto properties = CreatePropertiesForProxyWindow(gfx::Rect(0, 0, 400, 300));
-  std::unique_ptr<aura::Window> window(
-      GetWindowTreeTestHelper()->NewTopLevelWindow(
-          mojo::MapToFlatMap(properties)));
-
-  ws::ClientRootTestHelper client_root_test_helper(
-      ws::ProxyWindow::GetMayBeNull(window.get())
-          ->owning_window_tree()
-          ->GetClientRootForWindow(window.get()));
-  EXPECT_FALSE(client_root_test_helper.IsWindowForcedVisible());
-  // Assertions only make sense if window occlusion tracker is running, which it
-  // should be at this point.
-  EXPECT_FALSE(window->env()->GetWindowOcclusionTracker()->IsPaused());
-  auto widget = CreateTestWidget();
-  widget->Hide();
-  auto mirror_view = std::make_unique<WindowMirrorView>(
-      window.get(), /*trilinear_filtering_on_init=*/false);
-  widget->widget_delegate()->GetContentsView()->AddChildView(mirror_view.get());
-  // Even though the widget is hidden, the remote client should think it's
-  // visible.
-  EXPECT_TRUE(client_root_test_helper.IsWindowForcedVisible());
-}
-
-TEST_F(WindowMirrorViewTest, RemoteClientForcedVisibleWhenRunning) {
-  auto properties = CreatePropertiesForProxyWindow(gfx::Rect(0, 0, 400, 300));
-  std::unique_ptr<aura::Window> window(
-      GetWindowTreeTestHelper()->NewTopLevelWindow(
-          mojo::MapToFlatMap(properties)));
-
-  ws::ClientRootTestHelper client_root_test_helper(
-      ws::ProxyWindow::GetMayBeNull(window.get())
-          ->owning_window_tree()
-          ->GetClientRootForWindow(window.get()));
-
-  auto pause_occlusion_tracker =
-      std::make_unique<aura::WindowOcclusionTracker::ScopedPause>(
-          window->env());
-  auto widget = CreateTestWidget();
-  widget->Hide();
-  auto mirror_view = std::make_unique<WindowMirrorView>(
-      window.get(), /*trilinear_filtering_on_init=*/false);
-  widget->widget_delegate()->GetContentsView()->AddChildView(mirror_view.get());
-
-  // As occlusion tracking is paused, the remote window should remain hidden.
-  EXPECT_FALSE(client_root_test_helper.IsWindowForcedVisible());
-
-  // When occlusion tracking is enabled, the remote window shoul be visible.
-  pause_occlusion_tracker.reset();
-  EXPECT_TRUE(client_root_test_helper.IsWindowForcedVisible());
-}
-
 TEST_F(WindowMirrorViewTest, LocalWindowOcclusionMadeVisible) {
   auto widget = CreateTestWidget();
   widget->Hide();
diff --git a/ash/ws/ash_window_manager_unittest.cc b/ash/ws/ash_window_manager_unittest.cc
deleted file mode 100644
index 8c42919..0000000
--- a/ash/ws/ash_window_manager_unittest.cc
+++ /dev/null
@@ -1,59 +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 "ash/public/interfaces/ash_window_manager.mojom.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
-#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
-#include "ui/aura/mus/window_mus.h"
-#include "ui/aura/mus/window_tree_client.h"
-#include "ui/aura/test/env_test_helper.h"
-#include "ui/aura/test/mus/change_completion_waiter.h"
-#include "ui/aura/window.h"
-#include "ui/views/mus/mus_client.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-using AshWindowManagerTest = SingleProcessMashTestBase;
-
-TEST_F(AshWindowManagerTest, AddWindowToTabletMode) {
-  // Create a widget. This widget is backed by mus.
-  views::Widget widget;
-  views::Widget::InitParams params;
-  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  params.bounds = gfx::Rect(0, 0, 200, 200);
-  params.native_widget =
-      views::MusClient::Get()->CreateNativeWidget(params, &widget);
-  widget.Init(params);
-
-  // Flush all messages from the WindowTreeClient to ensure the window service
-  // has finished Widget creation.
-  aura::test::WaitForAllChangesToComplete();
-
-  // Turn on tablet mode.
-  Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
-  TabletModeWindowManager* tablet_wm =
-      TabletModeControllerTestApi().tablet_mode_window_manager();
-  EXPECT_EQ(0, tablet_wm->GetNumberOfManagedWindows());
-
-  // Call to AddWindowToTabletMode() over the mojom.
-  ash::mojom::AshWindowManagerAssociatedPtr ash_window_manager =
-      views::MusClient::Get()
-          ->window_tree_client()
-          ->BindWindowManagerInterface<ash::mojom::AshWindowManager>();
-  ash_window_manager->AddWindowToTabletMode(
-      aura::WindowMus::Get(widget.GetNativeWindow()->parent())->server_id());
-
-  // Ensures the message is processed by Ash.
-  ash_window_manager.FlushForTesting();
-
-  // The callback to AddWindowToTabletMode() should have been processed and
-  // added the window to the TabletModeWindowManager.
-  EXPECT_EQ(1, tablet_wm->GetNumberOfManagedWindows());
-}
-
-}  // namespace ash
diff --git a/ash/ws/multi_user_window_manager_bridge.cc b/ash/ws/multi_user_window_manager_bridge.cc
deleted file mode 100644
index d0f49e1..0000000
--- a/ash/ws/multi_user_window_manager_bridge.cc
+++ /dev/null
@@ -1,86 +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 "ash/ws/multi_user_window_manager_bridge.h"
-
-#include "ash/multi_user/multi_user_window_manager_impl.h"
-#include "ash/session/session_controller.h"
-#include "ash/shell.h"
-#include "services/ws/window_tree.h"
-#include "ui/aura/window.h"
-#include "ui/base/ui_base_features.h"
-
-namespace ash {
-
-MultiUserWindowManagerBridge::MultiUserWindowManagerBridge(
-    ws::WindowTree* window_tree,
-    mojo::ScopedInterfaceEndpointHandle handle)
-    : window_tree_(window_tree),
-      binding_(this,
-               mojo::AssociatedInterfaceRequest<mojom::MultiUserWindowManager>(
-                   std::move(handle))) {}
-
-MultiUserWindowManagerBridge::~MultiUserWindowManagerBridge() {
-  // We may get here after MultiUserWindowManager has been destroyed.
-  if (ash::MultiUserWindowManagerImpl::Get())
-    ash::MultiUserWindowManagerImpl::Get()->SetClient(nullptr);
-}
-
-void MultiUserWindowManagerBridge::SetClient(
-    mojom::MultiUserWindowManagerClientAssociatedPtrInfo client_info) {
-  multi_user_window_manager_.reset();
-  client_.Bind(std::move(client_info));
-  if (features::IsMultiProcessMash()) {
-    // NOTE: there is nothing stopping mulitple MultiUserWindowManagerBridges
-    // from being created (because multiple clients ask for
-    // ash::mojom::MultiUserWindowManager). This code is assuming only a single
-    // client is used at a time.
-    multi_user_window_manager_ =
-        std::make_unique<ash::MultiUserWindowManagerImpl>(
-            client_.get(), nullptr,
-            Shell::Get()->session_controller()->GetActiveAccountId());
-  } else if (ash::MultiUserWindowManagerImpl::Get()) {
-    ash::MultiUserWindowManagerImpl::Get()->SetClient(client_.get());
-  }
-}
-
-void MultiUserWindowManagerBridge::SetWindowOwner(ws::Id window_id,
-                                                  const AccountId& account_id,
-                                                  bool show_for_current_user) {
-  // AshTestBase pumps events during shutdown. This makes it possible to get
-  // here with no ash::MultiUserWindowManager. This should only be possible in
-  // tests. None-the-less this needs to be fixed for the multi-process case.
-  // http://crbug.com/875111.
-  if (!ash::MultiUserWindowManagerImpl::Get())
-    return;
-
-  aura::Window* window = window_tree_->GetWindowByTransportId(window_id);
-  if (window && window_tree_->IsTopLevel(window)) {
-    ash::MultiUserWindowManagerImpl::Get()->SetWindowOwner(
-        window, account_id, show_for_current_user, {window_id});
-  } else {
-    DVLOG(1) << "SetWindowOwner passed invalid window, id=" << window_id;
-  }
-}
-
-void MultiUserWindowManagerBridge::ShowWindowForUser(
-    ws::Id window_id,
-    const AccountId& account_id) {
-  // AshTestBase pumps events during shutdown. This makes it possible to get
-  // here with no ash::MultiUserWindowManager. This should only be possible in
-  // tests. None-the-less this needs to be fixed for the multi-process case.
-  // http://crbug.com/875111.
-  if (!ash::MultiUserWindowManagerImpl::Get())
-    return;
-
-  aura::Window* window = window_tree_->GetWindowByTransportId(window_id);
-  if (window && window_tree_->IsTopLevel(window)) {
-    ash::MultiUserWindowManagerImpl::Get()->ShowWindowForUser(window,
-                                                              account_id);
-  } else {
-    DVLOG(1) << "ShowWindowForUser passed invalid window, id=" << window_id;
-  }
-}
-
-}  // namespace ash
diff --git a/ash/ws/multi_user_window_manager_bridge.h b/ash/ws/multi_user_window_manager_bridge.h
deleted file mode 100644
index d3d35d0f6..0000000
--- a/ash/ws/multi_user_window_manager_bridge.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_WS_MULTI_USER_WINDOW_MANAGER_BRIDGE_H_
-#define ASH_WS_MULTI_USER_WINDOW_MANAGER_BRIDGE_H_
-
-#include "ash/public/interfaces/multi_user_window_manager.mojom.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
-#include "services/ws/common/types.h"
-#include "services/ws/window_manager_interface.h"
-
-namespace mojo {
-class ScopedInterfaceEndpointHandle;
-}
-
-namespace ws {
-class WindowTree;
-}
-
-namespace ash {
-
-class MultiUserWindowManagerImpl;
-
-// Responsible for forwarding calls to MultiUserWindowManager. In multi-process
-// mash mode *this* owns the MultiUserWindowManager. In all other cases chrome
-// owns the MultiUserWindowManager.
-class MultiUserWindowManagerBridge : public mojom::MultiUserWindowManager,
-                                     public ws::WindowManagerInterface {
- public:
-  MultiUserWindowManagerBridge(ws::WindowTree* window_tree,
-                               mojo::ScopedInterfaceEndpointHandle handle);
-  ~MultiUserWindowManagerBridge() override;
-
-  // mojom::MultiUserWindowManager overrides:
-  void SetClient(mojom::MultiUserWindowManagerClientAssociatedPtrInfo
-                     client_info) override;
-  void SetWindowOwner(ws::Id window_id,
-                      const AccountId& account_id,
-                      bool show_for_current_user) override;
-  void ShowWindowForUser(ws::Id window_id,
-                         const AccountId& account_id) override;
-
- private:
-  ws::WindowTree* window_tree_;
-  mojo::AssociatedBinding<mojom::MultiUserWindowManager> binding_;
-  mojom::MultiUserWindowManagerClientAssociatedPtr client_;
-  // Only valid in MultiUserWindowManager.
-  std::unique_ptr<ash::MultiUserWindowManagerImpl> multi_user_window_manager_;
-
-  DISALLOW_COPY_AND_ASSIGN(MultiUserWindowManagerBridge);
-};
-
-}  // namespace ash
-
-#endif  // ASH_WS_MULTI_USER_WINDOW_MANAGER_BRIDGE_H_
diff --git a/ash/ws/window_lookup_unittest.cc b/ash/ws/window_lookup_unittest.cc
deleted file mode 100644
index 8598e21..0000000
--- a/ash/ws/window_lookup_unittest.cc
+++ /dev/null
@@ -1,53 +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 "ash/ws/window_lookup.h"
-
-#include "ash/test/ash_test_base.h"
-#include "ui/aura/env.h"
-#include "ui/aura/test/mus/change_completion_waiter.h"
-#include "ui/aura/window.h"
-#include "ui/views/mus/mus_client.h"
-#include "ui/views/widget/widget.h"
-
-namespace ash {
-
-using WindowLookupTest = SingleProcessMashTestBase;
-
-TEST_F(WindowLookupTest, AddWindowToTabletMode) {
-  // Create a widget. This widget is backed by mus.
-  views::Widget widget;
-  views::Widget::InitParams params;
-  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-  params.bounds = gfx::Rect(0, 0, 200, 200);
-  params.native_widget =
-      views::MusClient::Get()->CreateNativeWidget(params, &widget);
-  widget.Init(params);
-
-  aura::Window* widget_root = widget.GetNativeWindow()->GetRootWindow();
-  ASSERT_TRUE(CurrentContext());
-  ASSERT_NE(widget_root, CurrentContext());
-
-  // At this point ash hasn't created the proxy (request is async, over mojo).
-  // So, there should be no proxy available yet.
-  EXPECT_FALSE(window_lookup::IsProxyWindow(widget_root));
-  EXPECT_FALSE(window_lookup::GetProxyWindowForClientWindow(widget_root));
-
-  // Flush all messages from the WindowTreeClient to ensure the window service
-  // has finished Widget creation.
-  aura::test::WaitForAllChangesToComplete();
-
-  // Now the proxy should be available.
-  EXPECT_FALSE(window_lookup::IsProxyWindow(widget_root));
-  aura::Window* proxy =
-      window_lookup::GetProxyWindowForClientWindow(widget_root);
-  ASSERT_TRUE(proxy);
-  EXPECT_NE(proxy, widget_root);
-  EXPECT_EQ(aura::Env::Mode::LOCAL, proxy->env()->mode());
-
-  // Ensure we can go back to the client created window.
-  EXPECT_EQ(widget_root, window_lookup::GetClientWindowForProxyWindow(proxy));
-}
-
-}  // namespace ash
diff --git a/ash/ws/window_service_delegate_impl.cc b/ash/ws/window_service_delegate_impl.cc
index c217e4f..acd5090 100644
--- a/ash/ws/window_service_delegate_impl.cc
+++ b/ash/ws/window_service_delegate_impl.cc
@@ -7,7 +7,6 @@
 #include "ash/accelerators/accelerator_controller.h"
 #include "ash/host/ash_window_tree_host.h"
 #include "ash/ime/ime_engine_factory_registry.h"
-#include "ash/public/interfaces/ash_window_manager.mojom.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/wm/container_finder.h"
@@ -18,7 +17,6 @@
 #include "ash/wm/window_finder.h"
 #include "ash/wm/window_util.h"
 #include "ash/ws/ash_window_manager.h"
-#include "ash/ws/multi_user_window_manager_bridge.h"
 #include "base/bind.h"
 #include "mojo/public/cpp/bindings/map.h"
 #include "services/ws/public/mojom/window_manager.mojom.h"
@@ -253,11 +251,6 @@
     mojo::ScopedInterfaceEndpointHandle handle) {
   if (name == mojom::AshWindowManager::Name_)
     return std::make_unique<AshWindowManager>(tree, std::move(handle));
-
-  if (name == mojom::MultiUserWindowManager::Name_) {
-    return std::make_unique<MultiUserWindowManagerBridge>(tree,
-                                                          std::move(handle));
-  }
   return nullptr;
 }
 
diff --git a/ash/ws/window_service_delegate_impl_unittest.cc b/ash/ws/window_service_delegate_impl_unittest.cc
deleted file mode 100644
index 2062c425..0000000
--- a/ash/ws/window_service_delegate_impl_unittest.cc
+++ /dev/null
@@ -1,572 +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 "ash/display/display_move_window_util.h"
-#include "ash/frame/non_client_frame_view_ash.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/wm/desks/desks_util.h"
-#include "ash/wm/resize_shadow.h"
-#include "ash/wm/resize_shadow_controller.h"
-#include "ash/wm/toplevel_window_event_handler.h"
-#include "ash/wm/window_state.h"
-#include "ash/ws/window_service_owner.h"
-#include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/test/bind_test_util.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "services/ws/event_injector.h"
-#include "services/ws/test_window_tree_client.h"
-#include "services/ws/window_tree_test_helper.h"
-#include "ui/aura/client/drag_drop_client.h"
-#include "ui/aura/client/drag_drop_delegate.h"
-#include "ui/aura/window.h"
-#include "ui/base/dragdrop/drag_drop_types.h"
-#include "ui/base/hit_test.h"
-#include "ui/display/display.h"
-#include "ui/display/screen.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/events/test/test_event_handler.h"
-
-namespace ash {
-
-namespace {
-
-// A testing DragDropDelegate that accepts any drops.
-class TestDragDropDelegate : public aura::client::DragDropDelegate {
- public:
-  TestDragDropDelegate() = default;
-  ~TestDragDropDelegate() override = default;
-
-  // aura::client::DragDropDelegate:
-  void OnDragEntered(const ui::DropTargetEvent& event) override {}
-  int OnDragUpdated(const ui::DropTargetEvent& event) override {
-    return ui::DragDropTypes::DRAG_MOVE;
-  }
-  void OnDragExited() override {}
-  int OnPerformDrop(const ui::DropTargetEvent& event) override {
-    return ui::DragDropTypes::DRAG_MOVE;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TestDragDropDelegate);
-};
-
-bool IsResizeShadowVisible(ResizeShadow* resize_shadow) {
-  if (!resize_shadow)
-    return false;
-  return resize_shadow->GetLayerForTest()->GetTargetVisibility();
-}
-
-}  // namespace
-
-// This test creates a top-level window via the WindowService in SetUp() and
-// provides some ease of use functions to access WindowService related state.
-class WindowServiceDelegateImplTest : public AshTestBase {
- public:
-  WindowServiceDelegateImplTest() = default;
-  ~WindowServiceDelegateImplTest() override = default;
-
-  ws::Id GetTopLevelWindowId() {
-    return GetWindowTreeTestHelper()->TransportIdForWindow(top_level_.get());
-  }
-
-  ToplevelWindowEventHandler* event_handler() {
-    return Shell::Get()->toplevel_window_event_handler();
-  }
-
-  std::vector<ws::Change>* GetWindowTreeClientChanges() {
-    return GetTestWindowTreeClient()->tracker()->changes();
-  }
-
-  void SetCanAcceptDrops() {
-    aura::client::SetDragDropDelegate(top_level_.get(),
-                                      &test_drag_drop_delegate_);
-  }
-
-  bool IsDragDropInProgress() {
-    return aura::client::GetDragDropClient(top_level_->GetRootWindow())
-        ->IsDragDropInProgress();
-  }
-
-  // AshTestBase:
-  void SetUp() override {
-    AshTestBase::SetUp();
-    Shell::Get()->aura_env()->set_throttle_input_on_resize_for_testing(false);
-    NonClientFrameViewAsh::use_empty_minimum_size_for_test_ = true;
-    top_level_ = CreateTestWindow(gfx::Rect(100, 100, 100, 100));
-    ASSERT_TRUE(top_level_);
-    GetEventGenerator()->PressLeftButton();
-  }
-  void TearDown() override {
-    // Ash owns the WindowTree, which also handles deleting |top_level_|. This
-    // needs to delete |top_level_| before the WindowTree is deleted, otherwise
-    // the WindowTree will delete |top_level_|, leading to a double delete.
-    top_level_.reset();
-    NonClientFrameViewAsh::use_empty_minimum_size_for_test_ = false;
-    AshTestBase::TearDown();
-  }
-
- protected:
-  TestDragDropDelegate test_drag_drop_delegate_;
-  std::unique_ptr<aura::Window> top_level_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(WindowServiceDelegateImplTest);
-};
-
-TEST_F(WindowServiceDelegateImplTest, RunWindowMoveLoop) {
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseTo(gfx::Point(5, 6));
-  EXPECT_EQ(gfx::Point(105, 106), top_level_->bounds().origin());
-  GetWindowTreeClientChanges()->clear();
-  GetEventGenerator()->ReleaseLeftButton();
-
-  // Releasing the mouse completes the move loop.
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=21 success=true"));
-  EXPECT_EQ(gfx::Point(105, 106), top_level_->bounds().origin());
-}
-
-TEST_F(WindowServiceDelegateImplTest, RunWindowMoveWithMultipleDisplays) {
-  UpdateDisplay("500x500,500x500");
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE,
-      top_level_->GetBoundsInScreen().origin(), HTCAPTION);
-  GetEventGenerator()->MoveMouseTo(gfx::Point(501, 1));
-  GetWindowTreeClientChanges()->clear();
-  GetEventGenerator()->ReleaseLeftButton();
-
-  EXPECT_EQ(Shell::GetRootWindowForDisplayId(GetSecondaryDisplay().id()),
-            top_level_->GetRootWindow());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "DisplayChanged window_id=0,1 display_id=2200000001"));
-  EXPECT_TRUE(ContainsChange(
-      *GetWindowTreeClientChanges(),
-      std::string("BoundsChanged window=0,1 bounds=500,0 100x100 "
-                  "local_surface_id=*")));
-}
-
-TEST_F(WindowServiceDelegateImplTest, SetWindowBoundsToDifferentDisplay) {
-  UpdateDisplay("500x500,500x500");
-  EXPECT_EQ(gfx::Point(100, 100), top_level_->GetBoundsInScreen().origin());
-
-  GetWindowTreeClientChanges()->clear();
-  GetWindowTreeTestHelper()->window_tree()->SetWindowBounds(
-      21, GetTopLevelWindowId(), gfx::Rect(600, 100, 100, 100), base::nullopt);
-  EXPECT_EQ(gfx::Point(600, 100), top_level_->GetBoundsInScreen().origin());
-  EXPECT_EQ(Shell::GetRootWindowForDisplayId(GetSecondaryDisplay().id()),
-            top_level_->GetRootWindow());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "DisplayChanged window_id=0,1 display_id=2200000001"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, DeleteWindowWithInProgressRunLoop) {
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      29, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  top_level_.reset();
-  EXPECT_FALSE(event_handler()->is_drag_in_progress());
-  // Deleting the window implicitly cancels the drag.
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=29 success=false"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, RunWindowMoveLoopInSecondaryDisplay) {
-  UpdateDisplay("500x400,500x400");
-  top_level_->SetBoundsInScreen(gfx::Rect(600, 100, 100, 100),
-                                GetSecondaryDisplay());
-
-  EXPECT_EQ(Shell::GetRootWindowForDisplayId(GetSecondaryDisplay().id()),
-            top_level_->GetRootWindow());
-  EXPECT_EQ(gfx::Point(600, 100), top_level_->GetBoundsInScreen().origin());
-
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE,
-      gfx::Point(605, 106), HTCAPTION);
-
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseTo(gfx::Point(615, 120));
-  EXPECT_EQ(gfx::Point(610, 114), top_level_->GetBoundsInScreen().origin());
-}
-
-TEST_F(WindowServiceDelegateImplTest, CancelWindowMoveLoop) {
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseTo(gfx::Point(5, 6));
-  EXPECT_EQ(gfx::Point(105, 106), top_level_->bounds().origin());
-  GetWindowTreeClientChanges()->clear();
-  GetWindowTreeTestHelper()->window_tree()->CancelWindowMove(
-      GetTopLevelWindowId());
-  EXPECT_FALSE(event_handler()->is_drag_in_progress());
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=21 success=false"));
-  EXPECT_EQ(gfx::Point(100, 100), top_level_->bounds().origin());
-}
-
-TEST_F(WindowServiceDelegateImplTest, WindowResize) {
-  gfx::Rect bounds = top_level_->bounds();
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTTOPLEFT);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseBy(5, 6);
-  bounds.Inset(5, 6, 0, 0);
-  EXPECT_EQ(bounds, top_level_->bounds());
-  GetWindowTreeClientChanges()->clear();
-  GetEventGenerator()->ReleaseLeftButton();
-
-  // Releasing the mouse completes the move loop.
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=21 success=true"));
-  EXPECT_EQ(bounds, top_level_->bounds());
-}
-
-TEST_F(WindowServiceDelegateImplTest, InvalidWindowComponent) {
-  gfx::Rect bounds = top_level_->bounds();
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE,
-      bounds.origin(), HTCLIENT);
-  EXPECT_FALSE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseTo(5, 6);
-  EXPECT_EQ(bounds, top_level_->bounds());
-  GetEventGenerator()->ReleaseLeftButton();
-}
-
-TEST_F(WindowServiceDelegateImplTest, NestedWindowMoveIsNotAllowed) {
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetWindowTreeClientChanges()->clear();
-
-  // Intentionally invokes PerformWindowMove to make sure it does not break
-  // anything.
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      22, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::TOUCH, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=22 success=false"));
-
-  GetWindowTreeClientChanges()->clear();
-  GetEventGenerator()->ReleaseLeftButton();
-  EXPECT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             "ChangeCompleted id=21 success=true"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, SetWindowResizeShadow) {
-  ResizeShadowController* controller = Shell::Get()->resize_shadow_controller();
-
-  GetWindowTreeTestHelper()->window_tree()->SetWindowResizeShadow(
-      GetTopLevelWindowId(), HTNOWHERE);
-  EXPECT_FALSE(IsResizeShadowVisible(
-      controller->GetShadowForWindowForTest(top_level_.get())));
-
-  GetWindowTreeTestHelper()->window_tree()->SetWindowResizeShadow(
-      GetTopLevelWindowId(), HTTOPLEFT);
-  ResizeShadow* shadow =
-      controller->GetShadowForWindowForTest(top_level_.get());
-  EXPECT_TRUE(IsResizeShadowVisible(shadow));
-  EXPECT_EQ(HTTOPLEFT, shadow->GetLastHitTestForTest());
-
-  // Nothing should change for invalid hit-test.
-  GetWindowTreeTestHelper()->window_tree()->SetWindowResizeShadow(
-      GetTopLevelWindowId(), HTCLIENT);
-  EXPECT_TRUE(IsResizeShadowVisible(shadow));
-  EXPECT_EQ(HTTOPLEFT, shadow->GetLastHitTestForTest());
-
-  GetWindowTreeTestHelper()->window_tree()->SetWindowResizeShadow(
-      GetTopLevelWindowId(), HTNOWHERE);
-  EXPECT_FALSE(IsResizeShadowVisible(shadow));
-}
-
-TEST_F(WindowServiceDelegateImplTest, RunDragLoop) {
-  SetCanAcceptDrops();
-  GetWindowTreeTestHelper()->window_tree()->PerformDragDrop(
-      21, GetTopLevelWindowId(), gfx::Point(),
-      base::flat_map<std::string, std::vector<uint8_t>>(), gfx::ImageSkia(),
-      gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE);
-
-  base::RunLoop run_loop;
-
-  // Post mouse move and release to allow the nested drag loop to pick it up.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindLambdaForTesting([this] {
-        ASSERT_TRUE(IsDragDropInProgress());
-
-        // Move mouse to center of |top_level_|.
-        GetEventGenerator()->MoveMouseTo(gfx::Point(150, 150));
-        GetWindowTreeClientChanges()->clear();
-        GetEventGenerator()->ReleaseLeftButton();
-      }));
-
-  // Let the drop loop task run.
-  run_loop.RunUntilIdle();
-
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "OnPerformDragDropCompleted id=21 success=true action=1"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, DeleteWindowWithInProgressDragLoop) {
-  SetCanAcceptDrops();
-  GetWindowTreeTestHelper()->window_tree()->PerformDragDrop(
-      21, GetTopLevelWindowId(), gfx::Point(),
-      base::flat_map<std::string, std::vector<uint8_t>>(), gfx::ImageSkia(),
-      gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE);
-
-  base::RunLoop run_loop;
-
-  // Post the task to delete the window to allow nested drag loop to pick it up.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindLambdaForTesting([this] {
-        ASSERT_TRUE(IsDragDropInProgress());
-
-        // Deletes the window.
-        top_level_.reset();
-
-        // Move mouse and release the button and it should not crash.
-        GetEventGenerator()->MoveMouseTo(gfx::Point(150, 150));
-        GetWindowTreeClientChanges()->clear();
-        GetEventGenerator()->ReleaseLeftButton();
-      }));
-
-  // Let the drop loop task run.
-  run_loop.RunUntilIdle();
-
-  // It fails because the target window |top_level_| is deleted.
-  EXPECT_TRUE(ContainsChange(
-      *GetWindowTreeClientChanges(),
-      "OnPerformDragDropCompleted id=21 success=false action=0"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, CancelDragDropBeforeDragLoopRun) {
-  SetCanAcceptDrops();
-  GetWindowTreeTestHelper()->window_tree()->PerformDragDrop(
-      21, GetTopLevelWindowId(), gfx::Point(),
-      base::flat_map<std::string, std::vector<uint8_t>>(), gfx::ImageSkia(),
-      gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE);
-
-  // Cancel the drag before the drag loop runs.
-  GetWindowTreeTestHelper()->window_tree()->CancelDragDrop(
-      GetTopLevelWindowId());
-
-  // Let the drop loop task run.
-  base::RunLoop().RunUntilIdle();
-
-  // It fails because the drag is canceled.
-  EXPECT_TRUE(ContainsChange(
-      *GetWindowTreeClientChanges(),
-      "OnPerformDragDropCompleted id=21 success=false action=0"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, CancelDragDropAfterDragLoopRun) {
-  SetCanAcceptDrops();
-  GetWindowTreeTestHelper()->window_tree()->PerformDragDrop(
-      21, GetTopLevelWindowId(), gfx::Point(),
-      base::flat_map<std::string, std::vector<uint8_t>>(), gfx::ImageSkia(),
-      gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE);
-
-  base::RunLoop run_loop;
-
-  // Post the task to cancel to allow nested drag loop to pick it up.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindLambdaForTesting([this] {
-        ASSERT_TRUE(IsDragDropInProgress());
-
-        GetWindowTreeTestHelper()->window_tree()->CancelDragDrop(
-            GetTopLevelWindowId());
-      }));
-
-  // Let the drop loop task run.
-  run_loop.RunUntilIdle();
-
-  // It fails because the drag is canceled.
-  EXPECT_TRUE(ContainsChange(
-      *GetWindowTreeClientChanges(),
-      "OnPerformDragDropCompleted id=21 success=false action=0"));
-}
-
-TEST_F(WindowServiceDelegateImplTest, ObserveTopmostWindow) {
-  std::unique_ptr<aura::Window> window2 =
-      CreateTestWindow(gfx::Rect(150, 100, 100, 100));
-  std::unique_ptr<aura::Window> window3(CreateTestWindowInShell(
-      SK_ColorRED, desks_util::GetActiveDeskContainerId(),
-      gfx::Rect(100, 150, 100, 100)));
-
-  // Left button is pressed on SetUp() -- release it first.
-  GetEventGenerator()->ReleaseLeftButton();
-  GetEventGenerator()->MoveMouseTo(gfx::Point(105, 105));
-  GetEventGenerator()->PressLeftButton();
-  GetWindowTreeClientChanges()->clear();
-
-  GetWindowTreeTestHelper()->window_tree()->ObserveTopmostWindow(
-      ws::mojom::MoveLoopSource::MOUSE, GetTopLevelWindowId());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=null"));
-  GetWindowTreeClientChanges()->clear();
-
-  GetEventGenerator()->MoveMouseTo(gfx::Point(155, 105));
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=0,2"));
-  GetWindowTreeClientChanges()->clear();
-
-  GetEventGenerator()->MoveMouseTo(gfx::Point(155, 115));
-  EXPECT_FALSE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=0,2"));
-  GetWindowTreeClientChanges()->clear();
-
-  GetEventGenerator()->MoveMouseTo(gfx::Point(155, 155));
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=null"));
-  GetWindowTreeClientChanges()->clear();
-
-  window3.reset();
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=0,2"));
-  GetWindowTreeClientChanges()->clear();
-
-  window2->Hide();
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     "TopmostWindowChanged window_id=0,1 window_id2=null"));
-  GetWindowTreeClientChanges()->clear();
-
-  GetWindowTreeTestHelper()->window_tree()->StopObservingTopmostWindow();
-}
-
-TEST_F(WindowServiceDelegateImplTest, MoveAcrossDisplays) {
-  UpdateDisplay("600x400,600+0-400x300");
-
-  GetWindowTreeClientChanges()->clear();
-
-  display::Screen* screen = display::Screen::GetScreen();
-  display::Display display1 = screen->GetPrimaryDisplay();
-  display::Display display2 = GetSecondaryDisplay();
-  EXPECT_EQ(display1.id(),
-            screen->GetDisplayNearestWindow(top_level_.get()).id());
-
-  GetWindowTreeTestHelper()->window_tree()->PerformWindowMove(
-      21, GetTopLevelWindowId(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
-      HTCAPTION);
-  EXPECT_TRUE(event_handler()->is_drag_in_progress());
-  GetEventGenerator()->MoveMouseTo(gfx::Point(610, 6));
-  GetWindowTreeClientChanges()->clear();
-  GetEventGenerator()->ReleaseLeftButton();
-
-  EXPECT_EQ(display2.id(),
-            screen->GetDisplayNearestWindow(top_level_.get()).id());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     std::string("DisplayChanged window_id=0,1 display_id=") +
-                         base::NumberToString(display2.id())));
-}
-
-TEST_F(WindowServiceDelegateImplTest, MoveActiveWindowBetweenDisplays) {
-  UpdateDisplay("600x400*2,300+0-400x300");
-
-  // This triggers a SetBounds call in middle of switching displays.
-  wm::GetWindowState(top_level_.get())
-      ->SetPreAutoManageWindowBounds(gfx::Rect(0, 0, 200, 250));
-
-  top_level_->SetBounds(gfx::Rect(0, 0, 100, 150));
-  ASSERT_TRUE(top_level_->CanFocus());
-  top_level_->Focus();
-
-  GetWindowTreeClientChanges()->clear();
-  display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
-
-  ASSERT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     std::string("DisplayChanged window_id=0,1 display_id=*")));
-  ASSERT_TRUE(ContainsChange(*GetWindowTreeClientChanges(),
-                             std::string("BoundsChanged window=0,1 bounds=*")));
-
-  // Verifies no "BoundsChanged" before "DisplayChanged".
-  bool found_bounds_change = false;
-  for (const auto& change : *GetWindowTreeClientChanges()) {
-    if (change.type == ws::CHANGE_TYPE_NODE_BOUNDS_CHANGED) {
-      found_bounds_change = true;
-    } else if (change.type == ws::CHANGE_TYPE_DISPLAY_CHANGED) {
-      EXPECT_FALSE(found_bounds_change);
-      break;
-    }
-  }
-}
-
-TEST_F(WindowServiceDelegateImplTest, RemoveDisplay) {
-  UpdateDisplay("500x400,500x400");
-  display::Display display1 = display::Screen::GetScreen()->GetPrimaryDisplay();
-  display::Display display2 = GetSecondaryDisplay();
-
-  GetWindowTreeClientChanges()->clear();
-  top_level_->SetBoundsInScreen(gfx::Rect(600, 100, 100, 100),
-                                GetSecondaryDisplay());
-  EXPECT_EQ(Shell::GetRootWindowForDisplayId(display2.id()),
-            top_level_->GetRootWindow());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     std::string("DisplayChanged window_id=0,1 display_id=") +
-                         base::NumberToString(display2.id())));
-
-  GetWindowTreeClientChanges()->clear();
-  UpdateDisplay("500x400");
-  EXPECT_EQ(Shell::GetRootWindowForDisplayId(display1.id()),
-            top_level_->GetRootWindow());
-  EXPECT_TRUE(
-      ContainsChange(*GetWindowTreeClientChanges(),
-                     std::string("DisplayChanged window_id=0,1 display_id=") +
-                         base::NumberToString(display1.id())));
-  EXPECT_TRUE(ContainsChange(
-      *GetWindowTreeClientChanges(),
-      std::string("BoundsChanged window=0,1 bounds=100,100 100x100 "
-                  "local_surface_id=*")));
-}
-
-TEST_F(WindowServiceDelegateImplTest, MultiDisplayEventInjector) {
-  UpdateDisplay("500x400,500x400");
-  display::Display display1 = display::Screen::GetScreen()->GetPrimaryDisplay();
-
-  top_level_->SetCapture();
-
-  top_level_->SetBoundsInScreen(gfx::Rect(450, 0, 200, 200),
-                                GetSecondaryDisplay());
-  EXPECT_NE(Shell::GetPrimaryRootWindow(), top_level_->GetRootWindow());
-
-  ws::EventInjector injector(
-      Shell::Get()->window_service_owner()->window_service());
-  base::RunLoop run_loop;
-  ui::test::TestEventHandler handler;
-  top_level_->AddPreTargetHandler(&handler);
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindLambdaForTesting([&]() {
-        auto ev = std::make_unique<ui::MouseEvent>(
-            ui::ET_MOUSE_DRAGGED, gfx::Point(470, 10), gfx::Point(470, 10),
-            base::TimeTicks::Now(), 0, 0);
-        static_cast<ws::mojom::EventInjector*>(&injector)->InjectEvent(
-            display1.id(), std::move(ev),
-            base::BindLambdaForTesting([&](bool result) {
-              EXPECT_TRUE(result);
-              run_loop.Quit();
-            }));
-      }));
-  run_loop.Run();
-  EXPECT_EQ(1, handler.num_mouse_events());
-  top_level_->RemovePreTargetHandler(&handler);
-}
-
-}  // namespace ash
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 631f415c..00f3b10b4 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2636,6 +2636,7 @@
     "task_runner_util_unittest.cc",
     "template_util_unittest.cc",
     "test/launcher/test_results_tracker_unittest.cc",
+    "test/launcher/unit_test_launcher_unittest.cc",
     "test/metrics/histogram_enum_reader_unittest.cc",
     "test/metrics/histogram_tester_unittest.cc",
     "test/metrics/user_action_tester_unittest.cc",
@@ -2830,6 +2831,7 @@
       "sync_socket_unittest.cc",
       "synchronization/waitable_event_watcher_unittest.cc",
       "test/launcher/test_results_tracker_unittest.cc",
+      "test/launcher/unit_test_launcher_unittest.cc",
     ]
 
     # Pull in specific Mac files for iOS (which have been filtered out by file
diff --git a/base/android/proguard/chromium_apk.flags b/base/android/proguard/chromium_apk.flags
index ac3d7f8..1df6a66b6 100644
--- a/base/android/proguard/chromium_apk.flags
+++ b/base/android/proguard/chromium_apk.flags
@@ -19,7 +19,9 @@
 # If we annotated all Parcelables that get put into Bundles other than
 # for saveInstanceState (e.g. PendingIntents), then we could actually keep the
 # names of just those ones. For now, we'll just keep them all.
--keepnames class * implements android.os.Parcelable
+-keepnames class * implements android.os.Parcelable {
+  <init>(...);
+}
 
 # Keep all enum values and valueOf methods. See
 # http://proguard.sourceforge.net/index.html#manual/examples.html
@@ -28,10 +30,6 @@
     public static **[] values();
 }
 
-# Keep classes implementing ParameterProvider -- these will be instantiated
-# via reflection.
--keep class * implements org.chromium.base.test.params.ParameterProvider
-
 # Allows Proguard freedom in removing these log related calls. We ask for debug
 # and verbose logs to be stripped out in base.Log, so we are just ensuring we
 # get rid of all other debug/verbose logs.
diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags
index 769e3d8c..6f1832d2 100644
--- a/base/android/proguard/chromium_code.flags
+++ b/base/android/proguard/chromium_code.flags
@@ -20,25 +20,25 @@
 -keep @interface org.chromium.base.annotations.UsedByReflection
 
 # Keeps for class level annotations.
--keep @org.chromium.base.annotations.UsedByReflection class * {}
+-keep @org.chromium.base.annotations.UsedByReflection class ** {}
 
 # Keeps for method level annotations.
--keepclasseswithmembers class * {
+-keepclasseswithmembers class ** {
   @org.chromium.base.annotations.AccessedByNative <fields>;
 }
--keepclasseswithmembers,includedescriptorclasses class * {
+-keepclasseswithmembers,includedescriptorclasses class ** {
   @org.chromium.base.annotations.CalledByNative <methods>;
 }
--keepclasseswithmembers,includedescriptorclasses class * {
+-keepclasseswithmembers,includedescriptorclasses class ** {
   @org.chromium.base.annotations.CalledByNativeUnchecked <methods>;
 }
--keepclasseswithmembers class * {
+-keepclasseswithmembers class ** {
   @org.chromium.base.annotations.UsedByReflection <methods>;
 }
--keepclasseswithmembers class * {
+-keepclasseswithmembers class ** {
   @org.chromium.base.annotations.UsedByReflection <fields>;
 }
--keepclasseswithmembers,includedescriptorclasses class * {
+-keepclasseswithmembers,includedescriptorclasses class ** {
   native <methods>;
 }
 
@@ -49,10 +49,10 @@
 
 # Never inline classes or methods with this annotation, but allow shrinking and
 # obfuscation.
--keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class * {
+-keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class ** {
   *;
 }
--keepclassmembernames,allowobfuscation class * {
+-keepclassmembernames,allowobfuscation class ** {
   @org.chromium.base.annotations.DoNotInline <methods>;
 }
 
@@ -65,7 +65,9 @@
 # If we annotated all Parcelables that get put into Bundles other than
 # for saveInstanceState (e.g. PendingIntents), then we could actually keep the
 # names of just those ones. For now, we'll just keep them all.
--keepnames class org.chromium.** implements android.os.Parcelable
+-keepnames class org.chromium.** implements android.os.Parcelable {
+  <init>(...);
+}
 
 # Keep all enum values and valueOf methods. See
 # http://proguard.sourceforge.net/index.html#manual/examples.html
diff --git a/base/android/proguard/disable_chromium_obfuscation.flags b/base/android/proguard/disable_chromium_obfuscation.flags
deleted file mode 100644
index b410239e..0000000
--- a/base/android/proguard/disable_chromium_obfuscation.flags
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright 2016 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.
-
-# Disables obfuscation for chromium packages.
--keepnames,allowoptimization class com.google.android.apps.chrome.**,org.chromium.** {
-  *;
-}
diff --git a/base/files/memory_mapped_file.cc b/base/files/memory_mapped_file.cc
index e7f344d0..6c8a0d9 100644
--- a/base/files/memory_mapped_file.cc
+++ b/base/files/memory_mapped_file.cc
@@ -46,6 +46,13 @@
     case READ_WRITE_EXTEND:
       // Can't open with "extend" because no maximum size is known.
       NOTREACHED();
+      break;
+#if defined(OS_WIN)
+    case READ_CODE_IMAGE:
+      flags |= File::FLAG_OPEN | File::FLAG_READ | File::FLAG_EXCLUSIVE_WRITE |
+               File::FLAG_EXECUTE;
+      break;
+#endif
   }
   file_.Initialize(file_name, flags);
 
@@ -90,6 +97,13 @@
         return false;
       }
       break;
+#if defined(OS_WIN)
+    case READ_CODE_IMAGE:
+      // Can't open with "READ_CODE_IMAGE", not supported outside Windows
+      // or with a |region|.
+      NOTREACHED();
+      break;
+#endif
   }
 
   if (IsValid())
diff --git a/base/files/memory_mapped_file.h b/base/files/memory_mapped_file.h
index 1f73bcc..8a8c320 100644
--- a/base/files/memory_mapped_file.h
+++ b/base/files/memory_mapped_file.h
@@ -44,6 +44,14 @@
     // needed. Note, however, that the maximum size will still be reserved
     // in the process address space.
     READ_WRITE_EXTEND,
+
+#if defined(OS_WIN)
+    // This provides read access, but as executable code used for prefetching
+    // DLLs into RAM to avoid inefficient hard fault patterns such as during
+    // process startup. The accessing thread could be paused while data from
+    // the file is read into memory (if needed).
+    READ_CODE_IMAGE,
+#endif
   };
 
   // The default constructor sets all members to invalid/null values.
@@ -84,10 +92,10 @@
     return Initialize(std::move(file), READ_ONLY);
   }
 
-  // As above, but works with a region of an already-opened file. All forms of
-  // |access| are allowed. If READ_WRITE_EXTEND is specified then |region|
-  // provides the maximum size of the file. If the memory mapping fails, it
-  // return false.
+  // As above, but works with a region of an already-opened file. |access|
+  // must not be READ_CODE_IMAGE. If READ_WRITE_EXTEND is specified then
+  // |region| provides the maximum size of the file. If the memory mapping
+  // fails, it return false.
   WARN_UNUSED_RESULT bool Initialize(File file,
                                      const Region& region,
                                      Access access);
@@ -115,6 +123,12 @@
                                            size_t* aligned_size,
                                            int32_t* offset);
 
+#if defined(OS_WIN)
+  // Maps the executable file to memory, set |data_| to that memory address.
+  // Return true on success.
+  bool MapImageToMemory(Access access);
+#endif
+
   // Map the file to memory, set data_ to that memory address. Return true on
   // success, false on any kind of failure. This is a helper for Initialize().
   bool MapFileRegionToMemory(const Region& region, Access access);
diff --git a/base/files/memory_mapped_file_win.cc b/base/files/memory_mapped_file_win.cc
index dbd39468..9735aeb4 100644
--- a/base/files/memory_mapped_file_win.cc
+++ b/base/files/memory_mapped_file_win.cc
@@ -20,11 +20,44 @@
 MemoryMappedFile::MemoryMappedFile() : data_(NULL), length_(0) {
 }
 
+bool MemoryMappedFile::MapImageToMemory(Access access) {
+  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
+
+  // The arguments to the calls of ::CreateFile(), ::CreateFileMapping(), and
+  // ::MapViewOfFile() need to be self consistent as far as access rights and
+  // type of mapping or one or more of them will fail in non-obvious ways.
+
+  if (!file_.IsValid())
+    return false;
+
+  file_mapping_.Set(::CreateFileMapping(file_.GetPlatformFile(), nullptr,
+                                        PAGE_EXECUTE_READ | SEC_IMAGE, 0, 0,
+                                        NULL));
+  if (!file_mapping_.IsValid())
+    return false;
+
+  data_ = static_cast<uint8_t*>(
+      ::MapViewOfFile(file_mapping_.Get(),
+                      FILE_MAP_READ | FILE_MAP_EXECUTE | SEC_IMAGE, 0, 0, 0));
+  if (!data_)
+    return false;
+
+  // We need to know how large the mapped file is in some cases
+  int64_t file_len = file_.GetLength();
+  if (!IsValueInRangeForNumericType<size_t>(file_len))
+    return false;
+
+  length_ = static_cast<size_t>(file_len);
+  return true;
+}
+
 bool MemoryMappedFile::MapFileRegionToMemory(
     const MemoryMappedFile::Region& region,
     Access access) {
   ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
 
+  DCHECK(access != READ_CODE_IMAGE || region == Region::kWholeFile);
+
   if (!file_.IsValid())
     return false;
 
@@ -41,6 +74,8 @@
       flags |= PAGE_READWRITE;
       size.QuadPart = region.size;
       break;
+    case READ_CODE_IMAGE:
+      return MapImageToMemory(access);
   }
 
   file_mapping_.Set(::CreateFileMapping(file_.GetPlatformFile(), NULL, flags,
@@ -68,8 +103,8 @@
     // which contains |region| and then add up the |data_offset| displacement.
     int64_t aligned_start = 0;
     size_t ignored = 0U;
-    CalculateVMAlignedBoundaries(
-        region.offset, region.size, &aligned_start, &ignored, &data_offset);
+    CalculateVMAlignedBoundaries(region.offset, region.size, &aligned_start,
+                                 &ignored, &data_offset);
     int64_t full_map_size = region.size + data_offset;
 
     // Ensure that the casts below in the MapViewOfFile call are sane.
diff --git a/base/test/launcher/test_launcher.h b/base/test/launcher/test_launcher.h
index 13aca16..b456bcd 100644
--- a/base/test/launcher/test_launcher.h
+++ b/base/test/launcher/test_launcher.h
@@ -79,7 +79,6 @@
   virtual size_t RetryTests(TestLauncher* test_launcher,
                             const std::vector<std::string>& test_names) = 0;
 
- protected:
   virtual ~TestLauncherDelegate();
 };
 
@@ -151,7 +150,8 @@
   // Constructor. |parallel_jobs| is the limit of simultaneous parallel test
   // jobs.
   TestLauncher(TestLauncherDelegate* launcher_delegate, size_t parallel_jobs);
-  ~TestLauncher();
+  // virtual to mock in testing.
+  virtual ~TestLauncher();
 
   // Runs the launcher. Must be called at most once.
   bool Run() WARN_UNUSED_RESULT;
@@ -161,7 +161,8 @@
   // command line. |observer|, if not null, is used to convey process lifetime
   // events to the caller. |observer| is destroyed after its OnCompleted
   // method is invoked.
-  void LaunchChildGTestProcess(
+  // virtual to mock in testing.
+  virtual void LaunchChildGTestProcess(
       const CommandLine& command_line,
       const std::string& wrapper,
       TimeDelta timeout,
diff --git a/base/test/launcher/test_results_tracker.cc b/base/test/launcher/test_results_tracker.cc
index 9d7c658..f1ee0d8 100644
--- a/base/test/launcher/test_results_tracker.cc
+++ b/base/test/launcher/test_results_tracker.cc
@@ -91,11 +91,12 @@
 
 TestResultsTracker::~TestResultsTracker() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK_GE(iteration_, 0);
 
   if (!out_)
     return;
 
+  DCHECK_GE(iteration_, 0);
+
   // Maps test case names to test results.
   typedef std::map<std::string, std::vector<TestResult> > TestCaseMap;
   TestCaseMap test_case_map;
diff --git a/base/test/launcher/test_results_tracker_unittest.cc b/base/test/launcher/test_results_tracker_unittest.cc
index 4f7cccbd..b2127a8 100644
--- a/base/test/launcher/test_results_tracker_unittest.cc
+++ b/base/test/launcher/test_results_tracker_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <stddef.h>
 
-#include "base/files/file.h"
+#include "base/base64.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/json/json_reader.h"
@@ -17,124 +17,263 @@
 namespace base {
 namespace {
 
-void ValidateTestResult(Value* root,
-                        const char* name,
-                        int elapsed_time,
-                        bool losless_snippet,
-                        const char* output,
-                        const char* output_base64,
-                        unsigned result_parts_count,
-                        const char* status) {
-  Value* val = root->FindKeyOfType(name, Value::Type::LIST);
-  ASSERT_TRUE(val);
-  ASSERT_EQ(1u, val->GetList().size());
-  val = &val->GetList().at(0);
-  ASSERT_TRUE(val->is_dict());
-
-  Value* value = val->FindKeyOfType("elapsed_time_ms", Value::Type::INTEGER);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(elapsed_time, value->GetInt());
-
-  value = val->FindKeyOfType("losless_snippet", Value::Type::BOOLEAN);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(losless_snippet, value->GetBool());
-
-  value = val->FindKeyOfType("output_snippet", Value::Type::STRING);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(output, value->GetString());
-
-  value = val->FindKeyOfType("output_snippet_base64", Value::Type::STRING);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(output_base64, value->GetString());
-
-  value = val->FindKeyOfType("result_parts", Value::Type::LIST);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(result_parts_count, value->GetList().size());
-
-  value = val->FindKeyOfType("status", Value::Type::STRING);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(status, value->GetString());
-}
-
-void ValidateStringList(Optional<Value>& root,
-                        const char* key,
-                        std::vector<const char*> values) {
-  Value* val = root->FindKeyOfType(key, Value::Type::LIST);
-  ASSERT_TRUE(val);
-  EXPECT_EQ(values.size(), val->GetList().size());
-  for (unsigned i = 0; i < values.size(); i++) {
-    ASSERT_TRUE(val->GetList().at(i).is_string());
-    EXPECT_EQ(values.at(i), val->GetList().at(i).GetString());
+// Unit tests to validate TestResultsTracker outputs the correct JSON file
+// given the correct setup.
+class TestResultsTrackerTester : public testing::Test {
+ protected:
+  void ValidateKeyValue(Value* dict_value,
+                        const std::string& key,
+                        int64_t expected_value) {
+    base::Optional<int> value = dict_value->FindIntKey(key);
+    ASSERT_TRUE(value);
+    EXPECT_EQ(expected_value, value.value());
   }
-}
 
-void ValidateTestLocation(Value* root,
-                          const char* key,
-                          const char* file,
-                          int line) {
-  Value* val = root->FindKeyOfType(key, Value::Type::DICTIONARY);
-  ASSERT_TRUE(val);
-  EXPECT_EQ(2u, val->DictSize());
+  void ValidateKeyValue(Value* dict_value,
+                        const std::string& key,
+                        int expected_value) {
+    base::Optional<int> value = dict_value->FindIntKey(key);
+    ASSERT_TRUE(value);
+    EXPECT_EQ(expected_value, value.value());
+  }
 
-  Value* value = val->FindKeyOfType("file", Value::Type::STRING);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(file, value->GetString());
+  void ValidateKeyValue(Value* dict_value,
+                        const std::string& key,
+                        bool expected_value) {
+    base::Optional<bool> value = dict_value->FindBoolKey(key);
+    ASSERT_TRUE(value);
+    EXPECT_EQ(expected_value, value.value());
+  }
 
-  value = val->FindKeyOfType("line", Value::Type::INTEGER);
-  ASSERT_TRUE(value);
-  EXPECT_EQ(line, value->GetInt());
-}
-}  // namespace
+  void ValidateKeyValue(Value* dict_value,
+                        const std::string& key,
+                        const std::string& expected_value) {
+    const std::string* value = dict_value->FindStringKey(key);
+    ASSERT_TRUE(value);
+    EXPECT_EQ(expected_value, *value);
+  }
 
-TEST(TestResultsTracker, SaveSummaryAsJSON) {
+  // Validate a json child node for a particular test result.
+  void ValidateTestResult(Value* root, TestResult& result) {
+    Value* val = root->FindListKey(result.full_name);
+    ASSERT_TRUE(val);
+    ASSERT_EQ(1u, val->GetList().size());
+    val = &val->GetList().at(0);
+    ASSERT_TRUE(val->is_dict());
+
+    ValidateKeyValue(val, "elapsed_time_ms",
+                     result.elapsed_time.InMilliseconds());
+    ValidateKeyValue(val, "losless_snippet", true);
+    ValidateKeyValue(val, "output_snippet", result.output_snippet);
+
+    std::string base64_output_snippet;
+    Base64Encode(result.output_snippet, &base64_output_snippet);
+    ValidateKeyValue(val, "output_snippet_base64", base64_output_snippet);
+    ValidateKeyValue(val, "status", result.StatusAsString());
+
+    Value* value = val->FindListKey("result_parts");
+    ASSERT_TRUE(value);
+    EXPECT_EQ(result.test_result_parts.size(), value->GetList().size());
+    for (unsigned i = 0; i < result.test_result_parts.size(); i++) {
+      TestResultPart result_part = result.test_result_parts.at(0);
+      Value* part_dict = &(value->GetList().at(i));
+      ASSERT_TRUE(part_dict);
+      ASSERT_TRUE(part_dict->is_dict());
+      ValidateKeyValue(part_dict, "type", result_part.TypeAsString());
+      ValidateKeyValue(part_dict, "file", result_part.file_name);
+      ValidateKeyValue(part_dict, "line", result_part.line_number);
+      ValidateKeyValue(part_dict, "summary", result_part.summary);
+      ValidateKeyValue(part_dict, "message", result_part.message);
+    }
+  }
+
+  void ValidateStringList(Optional<Value>& root,
+                          const std::string& key,
+                          std::vector<const char*> values) {
+    Value* val = root->FindListKey(key);
+    ASSERT_TRUE(val);
+    ASSERT_EQ(values.size(), val->GetList().size());
+    for (unsigned i = 0; i < values.size(); i++) {
+      ASSERT_TRUE(val->GetList().at(i).is_string());
+      EXPECT_EQ(values.at(i), val->GetList().at(i).GetString());
+    }
+  }
+
+  void ValidateTestLocation(Value* root,
+                            const std::string& key,
+                            const std::string& file,
+                            int line) {
+    Value* val = root->FindDictKey(key);
+    ASSERT_TRUE(val);
+    EXPECT_EQ(2u, val->DictSize());
+    ValidateKeyValue(val, "file", file);
+    ValidateKeyValue(val, "line", line);
+  }
+
+  TestResult GenerateTestResult(const std::string& test_name,
+                                TestResult::Status status,
+                                TimeDelta elapsed_td,
+                                const std::string& output_snippet) {
+    TestResult result;
+    result.full_name = test_name;
+    result.status = status;
+    result.elapsed_time = elapsed_td;
+    result.output_snippet = output_snippet;
+    return result;
+  }
+
+  TestResultPart GenerateTestResultPart(TestResultPart::Type type,
+                                        const std::string& file_name,
+                                        int line_number,
+                                        const std::string& summary,
+                                        const std::string& message) {
+    TestResultPart test_result_part;
+    test_result_part.type = type;
+    test_result_part.file_name = file_name;
+    test_result_part.line_number = line_number;
+    test_result_part.summary = summary;
+    test_result_part.message = message;
+    return test_result_part;
+  }
+
+  Optional<Value> SaveAndReadSummary() {
+    ScopedTempDir dir;
+    CHECK(dir.CreateUniqueTempDir());
+    FilePath path = dir.GetPath().AppendASCII("SaveSummaryResult.json");
+    CHECK(tracker.SaveSummaryAsJSON(path, std::vector<std::string>()));
+    File resultFile(path, File::FLAG_OPEN | File::FLAG_READ);
+    const int size = 2048;
+    std::string json;
+    CHECK(ReadFileToStringWithMaxSize(path, &json, size));
+    return JSONReader::Read(json);
+  }
+
   TestResultsTracker tracker;
+};
+
+// Validate JSON result file is saved with the correct structure.
+TEST_F(TestResultsTrackerTester, JsonSummaryEmptyResult) {
   tracker.OnTestIterationStarting();
-  tracker.AddGlobalTag("global1");
-  // tests should re-order in alphabetical order
-  tracker.AddTest("Test2");
-  tracker.AddTest("Test1");
-  tracker.AddTest("Test1");  // tests should only show once
-  tracker.AddTest("Test3");
-  tracker.AddDisabledTest("Test4");
-  tracker.AddTestLocation("Test1", "Test1File", 100);
-  tracker.AddTestPlaceholder("Test1");
-  tracker.GeneratePlaceholderIteration();
-  TestResult result;
-  result.full_name = "Test1";
-  result.status = TestResult::TEST_SUCCESS;
-  tracker.AddTestResult(result);
 
-  ScopedTempDir dir;
-  ASSERT_TRUE(dir.CreateUniqueTempDir());
-  FilePath path = dir.GetPath().AppendASCII("SaveSummaryResult.json");
-  ASSERT_TRUE(tracker.SaveSummaryAsJSON(path, std::vector<std::string>()));
-  File resultFile(path, File::FLAG_OPEN | File::FLAG_READ);
-  const int size = 2048;
-  std::string json;
-  ASSERT_TRUE(ReadFileToStringWithMaxSize(path, &json, size));
+  Optional<Value> root = SaveAndReadSummary();
 
-  Optional<Value> root = JSONReader::Read(json);
   ASSERT_TRUE(root);
   ASSERT_TRUE(root->is_dict());
   EXPECT_EQ(5u, root->DictSize());
+}
+
+// Validate global tags are saved correctly.
+TEST_F(TestResultsTrackerTester, JsonSummaryRootTags) {
+  tracker.OnTestIterationStarting();
+  tracker.AddTest("Test2");  // Test should appear in alphabetical order.
+  tracker.AddTest("Test1");
+  tracker.AddTest("Test3");
+  tracker.AddTest("Test1");  // Test should only appear once.
+  tracker.AddDisabledTest("Test3");
+  tracker.AddGlobalTag("global1");
+
+  Optional<Value> root = SaveAndReadSummary();
 
   ValidateStringList(root, "global_tags", {"global1"});
   ValidateStringList(root, "all_tests", {"Test1", "Test2", "Test3"});
-  ValidateStringList(root, "disabled_tests", {"Test4"});
+  ValidateStringList(root, "disabled_tests", {"Test3"});
+}
 
-  Value* val = root->FindKeyOfType("per_iteration_data", Value::Type::LIST);
+// Validate test locations are saved correctly.
+TEST_F(TestResultsTrackerTester, JsonSummaryTestLocation) {
+  tracker.OnTestIterationStarting();
+  tracker.AddTestLocation("Test1", "Test1File", 100);
+  tracker.AddTestLocation("Test2", "Test2File", 200);
+
+  Optional<Value> root = SaveAndReadSummary();
+
+  Value* val = root->FindDictKey("test_locations");
+
+  ASSERT_TRUE(val);
+  ASSERT_TRUE(val->is_dict());
+  EXPECT_EQ(2u, val->DictSize());
+
+  ValidateTestLocation(val, "Test1", "Test1File", 100);
+  ValidateTestLocation(val, "Test2", "Test2File", 200);
+}
+
+// Validate test results are saved correctly.
+TEST_F(TestResultsTrackerTester, JsonSummaryTestResults) {
+  TestResult test_result =
+      GenerateTestResult("Test", TestResult::TEST_SUCCESS,
+                         TimeDelta::FromMilliseconds(30), "output");
+  test_result.test_result_parts.push_back(GenerateTestResultPart(
+      TestResultPart::kSuccess, "TestFile", 110, "summary", "message"));
+  tracker.AddTestPlaceholder("Test");
+
+  tracker.OnTestIterationStarting();
+  tracker.GeneratePlaceholderIteration();
+  tracker.AddTestResult(test_result);
+
+  Optional<Value> root = SaveAndReadSummary();
+
+  Value* val = root->FindListKey("per_iteration_data");
   ASSERT_TRUE(val);
   ASSERT_EQ(1u, val->GetList().size());
-  val = &(val->GetList().at(0));
-  ASSERT_TRUE(val);
-  ASSERT_TRUE(val->is_dict());
-  ValidateTestResult(val, "Test1", 0, true, "", "", 0u, "SUCCESS");
 
-  val = root->FindKeyOfType("test_locations", Value::Type::DICTIONARY);
-  ASSERT_TRUE(val);
-  ASSERT_TRUE(val->is_dict());
-  EXPECT_EQ(1u, val->DictSize());
-  ValidateTestLocation(val, "Test1", "Test1File", 100);
+  Value* iteration_val = &(val->GetList().at(0));
+  ASSERT_TRUE(iteration_val);
+  ASSERT_TRUE(iteration_val->is_dict());
+  EXPECT_EQ(1u, iteration_val->DictSize());
+  ValidateTestResult(iteration_val, test_result);
 }
 
+// Validate test results without a placeholder.
+TEST_F(TestResultsTrackerTester, JsonSummaryTestWithoutPlaceholder) {
+  TestResult test_result =
+      GenerateTestResult("Test", TestResult::TEST_SUCCESS,
+                         TimeDelta::FromMilliseconds(30), "output");
+
+  tracker.OnTestIterationStarting();
+  tracker.GeneratePlaceholderIteration();
+  tracker.AddTestResult(test_result);
+
+  Optional<Value> root = SaveAndReadSummary();
+
+  Value* val = root->FindListKey("per_iteration_data");
+  ASSERT_TRUE(val);
+  ASSERT_EQ(1u, val->GetList().size());
+
+  Value* iteration_val = &(val->GetList().at(0));
+  // No result is saved since a placeholder was not specified.
+  EXPECT_EQ(0u, iteration_val->DictSize());
+}
+
+// Validate test results are saved correctly based on setup.
+TEST_F(TestResultsTrackerTester, JsonSummaryTestPlaceholderOrder) {
+  TestResult test_result =
+      GenerateTestResult("Test", TestResult::TEST_SUCCESS,
+                         TimeDelta::FromMilliseconds(30), "output");
+  TestResult test_result_empty = GenerateTestResult(
+      "Test", TestResult::TEST_NOT_RUN, TimeDelta::FromMilliseconds(0), "");
+
+  tracker.AddTestPlaceholder("Test");
+  // Test added before GeneratePlaceholderIteration, will not record.
+  tracker.OnTestIterationStarting();
+  tracker.AddTestResult(test_result);
+  tracker.GeneratePlaceholderIteration();
+  // This is the correct order of operations.
+  tracker.OnTestIterationStarting();
+  tracker.GeneratePlaceholderIteration();
+  tracker.AddTestResult(test_result);
+
+  Optional<Value> root = SaveAndReadSummary();
+  Value* val = root->FindListKey("per_iteration_data");
+  ASSERT_TRUE(val);
+  ASSERT_EQ(2u, val->GetList().size());
+
+  Value* iteration_val = &(val->GetList().at(0));
+  ValidateTestResult(iteration_val, test_result_empty);
+
+  iteration_val = &(val->GetList().at(1));
+  ValidateTestResult(iteration_val, test_result);
+}
+
+}  // namespace
+
 }  // namespace base
diff --git a/base/test/launcher/unit_test_launcher.cc b/base/test/launcher/unit_test_launcher.cc
index 787bc7c..2e63baa 100644
--- a/base/test/launcher/unit_test_launcher.cc
+++ b/base/test/launcher/unit_test_launcher.cc
@@ -15,7 +15,6 @@
 #include "base/compiler_specific.h"
 #include "base/debug/debugger.h"
 #include "base/files/file_util.h"
-#include "base/files/scoped_temp_dir.h"
 #include "base/format_macros.h"
 #include "base/location.h"
 #include "base/macros.h"
@@ -23,6 +22,7 @@
 #include "base/sequence_checker.h"
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
@@ -110,73 +110,6 @@
   fflush(stdout);
 }
 
-class DefaultUnitTestPlatformDelegate : public UnitTestPlatformDelegate {
- public:
-  DefaultUnitTestPlatformDelegate() = default;
-
- private:
-  // UnitTestPlatformDelegate:
-  bool GetTests(std::vector<TestIdentifier>* output) override {
-    *output = GetCompiledInTests();
-    return true;
-  }
-
-  bool CreateResultsFile(base::FilePath* path) override {
-    if (!CreateNewTempDirectory(FilePath::StringType(), path))
-      return false;
-    *path = path->AppendASCII("test_results.xml");
-    return true;
-  }
-
-  bool CreateTemporaryFile(base::FilePath* path) override {
-    if (!temp_dir_.IsValid() && !temp_dir_.CreateUniqueTempDir())
-      return false;
-    return CreateTemporaryFileInDir(temp_dir_.GetPath(), path);
-  }
-
-  CommandLine GetCommandLineForChildGTestProcess(
-      const std::vector<std::string>& test_names,
-      const base::FilePath& output_file,
-      const base::FilePath& flag_file) override {
-    CommandLine new_cmd_line(*CommandLine::ForCurrentProcess());
-
-    CHECK(base::PathExists(flag_file));
-
-    std::string long_flags(
-        std::string("--") + kGTestFilterFlag + "=" +
-        JoinString(test_names, ":"));
-    CHECK_EQ(static_cast<int>(long_flags.size()),
-             WriteFile(flag_file, long_flags.data(),
-                       static_cast<int>(long_flags.size())));
-
-    new_cmd_line.AppendSwitchPath(switches::kTestLauncherOutput, output_file);
-    new_cmd_line.AppendSwitchPath(kGTestFlagfileFlag, flag_file);
-    new_cmd_line.AppendSwitch(kSingleProcessTestsFlag);
-
-    return new_cmd_line;
-  }
-
-  std::string GetWrapperForChildGTestProcess() override {
-    return std::string();
-  }
-
-  void RelaunchTests(TestLauncher* test_launcher,
-                     const std::vector<std::string>& test_names,
-                     int launch_flags) override {
-    // Relaunch requested tests in parallel, but only use single
-    // test per batch for more precise results (crashes, etc).
-    for (const std::string& test_name : test_names) {
-      std::vector<std::string> batch;
-      batch.push_back(test_name);
-      RunUnitTestsBatch(test_launcher, this, batch, launch_flags);
-    }
-  }
-
-  ScopedTempDir temp_dir_;
-
-  DISALLOW_COPY_AND_ASSIGN(DefaultUnitTestPlatformDelegate);
-};
-
 bool GetSwitchValueAsInt(const std::string& switch_name, int* result) {
   if (!CommandLine::ForCurrentProcess()->HasSwitch(switch_name))
     return true;
@@ -686,6 +619,66 @@
       options, std::move(observer));
 }
 
+DefaultUnitTestPlatformDelegate::DefaultUnitTestPlatformDelegate() = default;
+
+bool DefaultUnitTestPlatformDelegate::GetTests(
+    std::vector<TestIdentifier>* output) {
+  *output = GetCompiledInTests();
+  return true;
+}
+
+bool DefaultUnitTestPlatformDelegate::CreateResultsFile(base::FilePath* path) {
+  if (!CreateNewTempDirectory(FilePath::StringType(), path))
+    return false;
+  *path = path->AppendASCII("test_results.xml");
+  return true;
+}
+
+bool DefaultUnitTestPlatformDelegate::CreateTemporaryFile(
+    base::FilePath* path) {
+  if (!temp_dir_.IsValid() && !temp_dir_.CreateUniqueTempDir())
+    return false;
+  return CreateTemporaryFileInDir(temp_dir_.GetPath(), path);
+}
+
+CommandLine DefaultUnitTestPlatformDelegate::GetCommandLineForChildGTestProcess(
+    const std::vector<std::string>& test_names,
+    const base::FilePath& output_file,
+    const base::FilePath& flag_file) {
+  CommandLine new_cmd_line(*CommandLine::ForCurrentProcess());
+
+  CHECK(base::PathExists(flag_file));
+
+  std::string long_flags(
+      StrCat({"--", kGTestFilterFlag, "=", JoinString(test_names, ":")}));
+  CHECK_EQ(static_cast<int>(long_flags.size()),
+           WriteFile(flag_file, long_flags.data(),
+                     static_cast<int>(long_flags.size())));
+
+  new_cmd_line.AppendSwitchPath(switches::kTestLauncherOutput, output_file);
+  new_cmd_line.AppendSwitchPath(kGTestFlagfileFlag, flag_file);
+  new_cmd_line.AppendSwitch(kSingleProcessTestsFlag);
+
+  return new_cmd_line;
+}
+
+std::string DefaultUnitTestPlatformDelegate::GetWrapperForChildGTestProcess() {
+  return std::string();
+}
+
+void DefaultUnitTestPlatformDelegate::RelaunchTests(
+    TestLauncher* test_launcher,
+    const std::vector<std::string>& test_names,
+    int launch_flags) {
+  // Relaunch requested tests in parallel, but only use single
+  // test per batch for more precise results (crashes, etc).
+  for (const std::string& test_name : test_names) {
+    std::vector<std::string> batch;
+    batch.push_back(test_name);
+    RunUnitTestsBatch(test_launcher, this, batch, launch_flags);
+  }
+}
+
 UnitTestLauncherDelegate::UnitTestLauncherDelegate(
     UnitTestPlatformDelegate* platform_delegate,
     size_t batch_limit,
diff --git a/base/test/launcher/unit_test_launcher.h b/base/test/launcher/unit_test_launcher.h
index 13e9889..32ba25d 100644
--- a/base/test/launcher/unit_test_launcher.h
+++ b/base/test/launcher/unit_test_launcher.h
@@ -12,6 +12,7 @@
 
 #include "base/callback.h"
 #include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/test/launcher/test_launcher.h"
 #include "build/build_config.h"
@@ -86,6 +87,39 @@
   ~UnitTestPlatformDelegate() = default;
 };
 
+// This default implementation uses gtest_util to get all
+// compiled gtests into the binary.
+// The delegate will relaunch test in parallel,
+// but only use single test per launch.
+class DefaultUnitTestPlatformDelegate : public UnitTestPlatformDelegate {
+ public:
+  DefaultUnitTestPlatformDelegate();
+
+ private:
+  // UnitTestPlatformDelegate:
+
+  bool GetTests(std::vector<TestIdentifier>* output) override;
+
+  bool CreateResultsFile(base::FilePath* path) override;
+
+  bool CreateTemporaryFile(base::FilePath* path) override;
+
+  CommandLine GetCommandLineForChildGTestProcess(
+      const std::vector<std::string>& test_names,
+      const base::FilePath& output_file,
+      const base::FilePath& flag_file) override;
+
+  std::string GetWrapperForChildGTestProcess() override;
+
+  void RelaunchTests(TestLauncher* test_launcher,
+                     const std::vector<std::string>& test_names,
+                     int launch_flags) override;
+
+  ScopedTempDir temp_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(DefaultUnitTestPlatformDelegate);
+};
+
 // Runs tests serially, each in its own process.
 void RunUnitTestsSerially(TestLauncher* test_launcher,
                           UnitTestPlatformDelegate* platform_delegate,
diff --git a/base/test/launcher/unit_test_launcher_unittest.cc b/base/test/launcher/unit_test_launcher_unittest.cc
new file mode 100644
index 0000000..8958900
--- /dev/null
+++ b/base/test/launcher/unit_test_launcher_unittest.cc
@@ -0,0 +1,167 @@
+// 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 <stddef.h>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/launcher/test_launcher.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+// Unit tests to validate DefaultUnitTestPlatformDelegate implementations.
+class DefaultUnitTestPlatformDelegateTester : public testing::Test {
+ protected:
+  UnitTestPlatformDelegate* platformDelegate;
+  FilePath flag_path;
+  FilePath output_path;
+  std::vector<std::string> test_names;
+
+  void SetUp() override { platformDelegate = &defaultPlatform_; }
+
+ private:
+  DefaultUnitTestPlatformDelegate defaultPlatform_;
+};
+
+// Call fails when flag_file does not exist.
+TEST_F(DefaultUnitTestPlatformDelegateTester, FlagPathCheckFail) {
+  ASSERT_CHECK_DEATH(platformDelegate->GetCommandLineForChildGTestProcess(
+      test_names, output_path, flag_path));
+}
+
+// Validate flags are set correctly in by the delegate.
+TEST_F(DefaultUnitTestPlatformDelegateTester,
+       GetCommandLineForChildGTestProcess) {
+  ASSERT_TRUE(platformDelegate->CreateResultsFile(&output_path));
+  ASSERT_TRUE(platformDelegate->CreateTemporaryFile(&flag_path));
+  CommandLine cmd_line(platformDelegate->GetCommandLineForChildGTestProcess(
+      test_names, output_path, flag_path));
+  EXPECT_EQ(cmd_line.GetSwitchValueASCII("test-launcher-output"),
+            output_path.MaybeAsASCII());
+  EXPECT_EQ(cmd_line.GetSwitchValueASCII("gtest_flagfile"),
+            flag_path.MaybeAsASCII());
+  EXPECT_TRUE(cmd_line.HasSwitch("single-process-tests"));
+}
+
+// Validate the tests are saved correctly in flag file under
+// the "--gtest_filter" flag.
+TEST_F(DefaultUnitTestPlatformDelegateTester, GetCommandLineFilterTest) {
+  test_names.push_back("Test1");
+  test_names.push_back("Test2");
+  ASSERT_TRUE(platformDelegate->CreateResultsFile(&output_path));
+  ASSERT_TRUE(platformDelegate->CreateTemporaryFile(&flag_path));
+  CommandLine cmd_line(platformDelegate->GetCommandLineForChildGTestProcess(
+      test_names, output_path, flag_path));
+
+  const int size = 2048;
+  std::string content;
+  ASSERT_TRUE(ReadFileToStringWithMaxSize(flag_path, &content, size));
+  EXPECT_EQ(content.find("--gtest_filter="), 0u);
+  base::ReplaceSubstringsAfterOffset(&content, 0, "--gtest_filter=", "");
+  std::vector<std::string> gtest_filter_tests =
+      SplitString(content, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  ASSERT_EQ(gtest_filter_tests.size(), test_names.size());
+  for (unsigned i = 0; i < test_names.size(); i++) {
+    EXPECT_EQ(gtest_filter_tests.at(i), test_names.at(i));
+  }
+}
+
+// Mock TestLauncher to validate LaunchChildGTestProcess
+// is called correctly inside the test launcher delegate.
+class MockTestLauncher : public TestLauncher {
+ public:
+  MockTestLauncher(TestLauncherDelegate* launcher_delegate,
+                   size_t parallel_jobs)
+      : TestLauncher(launcher_delegate, parallel_jobs) {}
+
+  MOCK_METHOD5(LaunchChildGTestProcess,
+               void(const CommandLine& command_line,
+                    const std::string& wrapper,
+                    TimeDelta timeout,
+                    const LaunchOptions& options,
+                    std::unique_ptr<ProcessLifetimeObserver> observer));
+};
+
+// Unit tests to validate UnitTestLauncherDelegateTester implementations.
+class UnitTestLauncherDelegateTester : public testing::Test {
+ protected:
+  TestLauncherDelegate* launcherDelegate;
+  MockTestLauncher* launcher;
+  std::vector<std::string> tests;
+
+  void SetUp() override { tests.assign(100, "Test"); }
+
+  // Setup test launcher delegate with a particular batch size.
+  void SetUpLauncherDelegate(size_t batch_size) {
+    launcherDelegate =
+        new UnitTestLauncherDelegate(&defaultPlatform, batch_size, true);
+    launcher = new MockTestLauncher(launcherDelegate, batch_size);
+  }
+
+  // Validate LaunchChildGTestProcess is called x number of times.
+  void ValidateChildGTestProcessCalls(int times_called) {
+    using ::testing::_;
+    EXPECT_CALL(*launcher, LaunchChildGTestProcess(_, _, _, _, _))
+        .Times(times_called);
+  }
+
+  void TearDown() override {
+    delete launcherDelegate;
+    delete launcher;
+  }
+
+ private:
+  DefaultUnitTestPlatformDelegate defaultPlatform;
+};
+
+// Validate 0 batch size corresponds to unlimited batch size.
+TEST_F(UnitTestLauncherDelegateTester, RunTestsWithUnlimitedBatchSize) {
+  SetUpLauncherDelegate(0);
+
+  ValidateChildGTestProcessCalls(1);
+  EXPECT_EQ(launcherDelegate->RunTests(launcher, tests), tests.size());
+}
+
+// Validate edge case, no tests to run.
+TEST_F(UnitTestLauncherDelegateTester, RunTestsWithEmptyTests) {
+  SetUpLauncherDelegate(0);
+
+  ValidateChildGTestProcessCalls(0);
+  tests.clear();
+  EXPECT_EQ(launcherDelegate->RunTests(launcher, tests), tests.size());
+}
+
+// Validate delegate slices batch size correctly.
+TEST_F(UnitTestLauncherDelegateTester, RunTestsBatchSize10) {
+  SetUpLauncherDelegate(10);
+
+  ValidateChildGTestProcessCalls(10);
+  EXPECT_EQ(launcherDelegate->RunTests(launcher, tests), tests.size());
+}
+
+// ValidateRetryTests will only kick-off one run.
+TEST_F(UnitTestLauncherDelegateTester, RetryTests) {
+  // ScopedTaskEviorment is needed since RetryTests uses thread task
+  // runner to start.
+  test::ScopedTaskEnvironment task_environment;
+  SetUpLauncherDelegate(10);
+
+  ValidateChildGTestProcessCalls(1);
+  EXPECT_EQ(launcherDelegate->RetryTests(launcher, tests), tests.size());
+  RunLoop().RunUntilIdle();
+}
+
+}  // namespace
+
+}  // namespace base
diff --git a/build/android/pylib/local/machine/local_machine_junit_test_run.py b/build/android/pylib/local/machine/local_machine_junit_test_run.py
index dbfc505d..6d2d3c8 100644
--- a/build/android/pylib/local/machine/local_machine_junit_test_run.py
+++ b/build/android/pylib/local/machine/local_machine_junit_test_run.py
@@ -103,7 +103,7 @@
           jacoco_agent_path = os.path.join(host_paths.DIR_SOURCE_ROOT,
                                            'third_party', 'jacoco', 'lib',
                                            'jacocoagent.jar')
-          jacoco_args = '-javaagent:{}=destfile={},includes=org.chromium.*'
+          jacoco_args = '-javaagent:{}=destfile={},inclnolocationclasses=true'
           jvm_args.append(
               jacoco_args.format(jacoco_agent_path, jacoco_coverage_file))
         else:
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index b6f0ba1..a0b88163 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -4122,7 +4122,7 @@
         jar_path = "$_output_path/classes.jar"
         output_name = invoker.target_name
 
-        if (_scanned_files.has_proguard_flags) {
+        if (!_ignore_proguard_configs && _scanned_files.has_proguard_flags) {
           if (!defined(proguard_configs)) {
             proguard_configs = []
           }
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index c0dc26f..09ad3f6 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -170,10 +170,7 @@
 
 declare_args() {
   # Set to true to use lld, the LLVM linker.
-  # https://crbug.com/917504 for arm chromeos
-  use_lld = is_clang && (is_win || is_fuchsia || is_android ||
-                         (is_linux && target_os != "chromeos") ||
-                         (target_os == "chromeos" && current_cpu != "arm"))
+  use_lld = is_clang && (is_win || is_fuchsia || is_android || is_linux)
 }
 
 declare_args() {
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index e26a2ee..c3909ef 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -272,7 +272,9 @@
     "//base:base_java",
     "//base:jni_java",
     "//chrome/android/features/keyboard_accessory:public_java",
+    "//chrome/android/public/crypto:java",
     "//chrome/android/public/lifecycle:java",
+    "//chrome/android/public/profiles:java",
     "//chrome/android/third_party/compositor_animator:compositor_animator_java",
     "//chrome/android/webapk/libs/client:client_java",
     "//chrome/android/webapk/libs/common:common_java",
@@ -490,6 +492,7 @@
     "//chrome/android/features/autofill_assistant:jni_headers",
     "//chrome/android/features/keyboard_accessory:jni_headers",
     "//chrome/android/features/media_router:jni_headers",
+    "//chrome/android/public/profiles:jni_headers",
   ]
 }
 
@@ -728,6 +731,8 @@
     "//chrome/android:app_hooks_java",
     "//chrome/android:chrome_java",
     "//chrome/android/features/tab_ui:java",
+    "//chrome/android/public/crypto:java",
+    "//chrome/android/public/profiles:java",
     "//chrome/android/third_party/compositor_animator:compositor_animator_java",
     "//chrome/android/webapk/libs/client:client_java",
     "//chrome/android/webapk/libs/common:common_java",
@@ -1552,6 +1557,7 @@
     ":chrome_java",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//chrome/android/public/profiles:java",
     "//components/offline_items_collection/core:core_java",
     "//components/sync:test_support_proto_java",
     "//content/public/android:content_java",
@@ -2409,7 +2415,6 @@
     "java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java",
     "java/src/org/chromium/chrome/browser/contextualsearch/CtrSuppression.java",
     "java/src/org/chromium/chrome/browser/contextualsearch/SimpleSearchTermResolver.java",
-    "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
     "java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java",
     "java/src/org/chromium/chrome/browser/crash/PureJavaExceptionHandler.java",
     "java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java",
@@ -2500,7 +2505,6 @@
     "java/src/org/chromium/chrome/browser/notifications/NotificationSystemStatusUtil.java",
     "java/src/org/chromium/chrome/browser/notifications/NotificationTriggerScheduler.java",
     "java/src/org/chromium/chrome/browser/notifications/scheduler/NotificationSchedulerTask.java",
-    "java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotifier.java",
     "java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java",
     "java/src/org/chromium/chrome/browser/ntp/LogoBridge.java",
     "java/src/org/chromium/chrome/browser/ntp/RecentTabsPagePrefs.java",
@@ -2560,9 +2564,6 @@
     "java/src/org/chromium/chrome/browser/prerender/ExternalPrerenderHandler.java",
     "java/src/org/chromium/chrome/browser/previews/PreviewsAndroidBridge.java",
     "java/src/org/chromium/chrome/browser/printing/TabPrinter.java",
-    "java/src/org/chromium/chrome/browser/profiles/Profile.java",
-    "java/src/org/chromium/chrome/browser/profiles/ProfileDownloader.java",
-    "java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
     "java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java",
     "java/src/org/chromium/chrome/browser/push_messaging/PushMessagingServiceObserver.java",
     "java/src/org/chromium/chrome/browser/rappor/RapporServiceBridge.java",
@@ -2576,6 +2577,7 @@
     "java/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfInfoBar.java",
     "java/src/org/chromium/chrome/browser/sessions/SessionTabHelper.java",
     "java/src/org/chromium/chrome/browser/signin/IdentityServicesProvider.java",
+    "java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java",
     "java/src/org/chromium/chrome/browser/signin/SigninInvestigator.java",
     "java/src/org/chromium/chrome/browser/signin/SigninManager.java",
     "java/src/org/chromium/chrome/browser/signin/SigninPromoUtil.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 32dea83..e634fb8 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -371,8 +371,6 @@
   "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapWordEdgeSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapWordLengthSuppression.java",
-  "java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
-  "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
   "java/src/org/chromium/chrome/browser/coordinator/CoordinatorLayoutForPointer.java",
   "java/src/org/chromium/chrome/browser/crash/ApplicationStatusTracker.java",
   "java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobService.java",
@@ -383,8 +381,6 @@
   "java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java",
   "java/src/org/chromium/chrome/browser/crash/PureJavaExceptionHandler.java",
   "java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java",
-  "java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java",
-  "java/src/org/chromium/chrome/browser/crypto/CipherFactory.java",
   "java/src/org/chromium/chrome/browser/customtabs/ClientManager.java",
   "java/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigator.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomButtonParams.java",
@@ -939,7 +935,6 @@
   "java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java",
   "java/src/org/chromium/chrome/browser/notifications/channels/SiteChannelsManager.java",
   "java/src/org/chromium/chrome/browser/notifications/scheduler/NotificationSchedulerTask.java",
-  "java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotifier.java",
   "java/src/org/chromium/chrome/browser/ntp/FakeRecentlyClosedTabManager.java",
   "java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java",
   "java/src/org/chromium/chrome/browser/ntp/IncognitoNewTabPage.java",
@@ -1347,9 +1342,6 @@
   "java/src/org/chromium/chrome/browser/previews/PreviewsUma.java",
   "java/src/org/chromium/chrome/browser/printing/PrintShareActivity.java",
   "java/src/org/chromium/chrome/browser/printing/TabPrinter.java",
-  "java/src/org/chromium/chrome/browser/profiles/Profile.java",
-  "java/src/org/chromium/chrome/browser/profiles/ProfileDownloader.java",
-  "java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
   "java/src/org/chromium/chrome/browser/provider/BaseColumns.java",
   "java/src/org/chromium/chrome/browser/provider/BookmarkColumns.java",
   "java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java",
@@ -1404,6 +1396,7 @@
   "java/src/org/chromium/chrome/browser/signin/GoogleActivityController.java",
   "java/src/org/chromium/chrome/browser/signin/PersonalizedSigninPromoView.java",
   "java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java",
+  "java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java",
   "java/src/org/chromium/chrome/browser/signin/SignOutDialogFragment.java",
   "java/src/org/chromium/chrome/browser/signin/SigninActivity.java",
   "java/src/org/chromium/chrome/browser/signin/SigninFragment.java",
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 5728c75..8841b1ec 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -1212,13 +1212,6 @@
         <receiver android:name="org.chromium.chrome.browser.notifications.NotificationIntentInterceptor$Receiver"
             android:exported="false"/>
 
-        <receiver android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$OpenUrlReceiver"
-            android:exported="false"/>
-        <receiver android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$DeleteReceiver"
-            android:exported="false"/>
-        <receiver android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$TimeoutReceiver"
-            android:exported="false"/>
-
         <receiver android:name="org.chromium.chrome.browser.send_tab_to_self.NotificationManager$TapReceiver"
             android:exported="false"/>
         <receiver android:name="org.chromium.chrome.browser.send_tab_to_self.NotificationManager$DeleteReceiver"
diff --git a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
index 2748d5a8..44ec994c 100644
--- a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
@@ -1291,15 +1291,6 @@
     </receiver>
     <receiver
         android:exported="false"
-        android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$DeleteReceiver"/>
-    <receiver
-        android:exported="false"
-        android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$OpenUrlReceiver"/>
-    <receiver
-        android:exported="false"
-        android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotifier$TimeoutReceiver"/>
-    <receiver
-        android:exported="false"
         android:name="org.chromium.chrome.browser.offlinepages.AutoFetchNotifier$CompleteNotificationReceiver"/>
     <receiver
         android:exported="false"
diff --git a/chrome/android/java/res/layout/personalized_signin_promo_view_header.xml b/chrome/android/java/res/layout/personalized_signin_promo_view_header.xml
index 7f0e000..b4ac636 100644
--- a/chrome/android/java/res/layout/personalized_signin_promo_view_header.xml
+++ b/chrome/android/java/res/layout/personalized_signin_promo_view_header.xml
@@ -4,8 +4,9 @@
      found in the LICENSE file. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:tools="http://schemas.android.com/tools"
-       tools:showIn="@layout/personalized_signin_promo_view_settings">
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    tools:showIn="@layout/personalized_signin_promo_view_settings">
 
     <ImageView
         android:id="@+id/signin_promo_image"
@@ -15,7 +16,7 @@
         tools:ignore="ContentDescription"
         android:scaleType="fitCenter"/>
 
-    <ImageButton
+    <org.chromium.ui.widget.ChromeImageButton
         android:id="@+id/signin_promo_close_button"
         android:layout_width="24dp"
         android:layout_height="24dp"
@@ -24,5 +25,6 @@
         android:contentDescription="@string/close"
         android:scaleType="fitCenter"
         android:src="@drawable/btn_close"
-        android:visibility="gone"/>
+        android:visibility="gone"
+        app:tint="@color/default_icon_color" />
 </merge>
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 2a64da80..3a5213b6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -196,8 +196,6 @@
     public static final String CHROME_SMART_SELECTION = "ChromeSmartSelection";
     public static final String CLEAR_OLD_BROWSING_DATA = "ClearOldBrowsingData";
     public static final String COMMAND_LINE_ON_NON_ROOTED = "CommandLineOnNonRooted";
-    public static final String CONTENT_SUGGESTIONS_NOTIFICATIONS =
-            "ContentSuggestionsNotifications";
     public static final String CONTENT_SUGGESTIONS_SCROLL_TO_LOAD =
             "ContentSuggestionsScrollToLoad";
     public static final String CONTEXTUAL_SEARCH_DEFINITIONS = "ContextualSearchDefinitions";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBar.java
index 2354fc1..a346fb1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBar.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
+import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.Priority;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -133,18 +134,18 @@
             icon = ApiCompatibilityUtils.getDrawable(getContext().getResources(), mIconDrawableId);
         }
 
+        ActionNames names = new ActionNames();
+        names.cancel = mContext.getResources().getString(R.string.cancel);
+        names.select = mContext.getResources().getString(R.string.select);
+        names.alt = "";
         PropertyModel model =
                 new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS)
                         .with(TouchlessDialogProperties.IS_FULLSCREEN, false)
                         .with(TouchlessDialogProperties.PRIORITY, Priority.HIGH)
+                        .with(TouchlessDialogProperties.ACTION_NAMES, names)
                         .with(TouchlessDialogProperties.CANCEL_ACTION,
                                 view -> onCloseButtonClicked())
-                        .with(TouchlessDialogProperties.CANCEL_NAME,
-                                mContext.getResources().getString(R.string.cancel))
-                        .with(TouchlessDialogProperties.SELECT_NAME,
-                                mContext.getResources().getString(R.string.select))
                         .with(TouchlessDialogProperties.ALT_ACTION, null)
-                        .with(TouchlessDialogProperties.ALT_NAME, null)
                         .with(ModalDialogProperties.TITLE, mMessage.toString())
                         .with(ModalDialogProperties.TITLE_ICON, icon)
                         .with(ModalDialogProperties.CONTROLLER,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotifier.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotifier.java
deleted file mode 100644
index 109b7e7..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotifier.java
+++ /dev/null
@@ -1,555 +0,0 @@
-// Copyright 2016 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.ntp;
-
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Build;
-import android.provider.Browser;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.library_loader.LibraryProcessType;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.IntentHandler;
-import org.chromium.chrome.browser.ShortcutHelper;
-import org.chromium.chrome.browser.document.ChromeLauncherActivity;
-import org.chromium.chrome.browser.notifications.ChromeNotification;
-import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
-import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
-import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
-import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
-import org.chromium.chrome.browser.notifications.NotificationMetadata;
-import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
-import org.chromium.chrome.browser.notifications.PendingIntentProvider;
-import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
-import org.chromium.chrome.browser.notifications.channels.ChannelsInitializer;
-import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction;
-import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationOptOut;
-import org.chromium.chrome.browser.preferences.NotificationsPreferences;
-import org.chromium.chrome.browser.preferences.PreferencesLauncher;
-import org.chromium.content_public.browser.BrowserStartupController;
-import org.chromium.content_public.browser.BrowserStartupController.StartupCallback;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Provides functionality needed for content suggestion notifications.
- *
- * Exposes helper functions to native C++ code.
- */
-public class ContentSuggestionsNotifier {
-    private static final String NOTIFICATION_TAG = "ContentSuggestionsNotification";
-    private static final String NOTIFICATION_ID_EXTRA = "notification_id";
-    private static final String NOTIFICATION_CATEGORY_EXTRA = "category";
-    private static final String NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA = "id_within_category";
-
-    private static final String PREF_CHANNEL_CREATED =
-            "ntp.content_suggestions.notification.channel_created";
-
-    private static final String PREF_CACHED_ACTION_TAP =
-            "ntp.content_suggestions.notification.cached_action_tap";
-    private static final String PREF_CACHED_ACTION_DISMISSAL =
-            "ntp.content_suggestions.notification.cached_action_dismissal";
-    private static final String PREF_CACHED_ACTION_HIDE_DEADLINE =
-            "ntp.content_suggestions.notification.cached_action_hide_deadline";
-    private static final String PREF_CACHED_ACTION_HIDE_EXPIRY =
-            "ntp.content_suggestions.notification.cached_action_hide_expiry";
-    private static final String PREF_CACHED_ACTION_HIDE_FRONTMOST =
-            "ntp.content_suggestions.notification.cached_action_hide_frontmost";
-    private static final String PREF_CACHED_ACTION_HIDE_DISABLED =
-            "ntp.content_suggestions.notification.cached_action_hide_disabled";
-    private static final String PREF_CACHED_ACTION_HIDE_SHUTDOWN =
-            "ntp.content_suggestions.notification.cached_action_hide_shutdown";
-    private static final String PREF_CACHED_CONSECUTIVE_IGNORED =
-            "ntp.content_suggestions.notification.cached_consecutive_ignored";
-
-    // Tracks which URIs there is an active notification for.
-    private static final String PREF_ACTIVE_NOTIFICATIONS =
-            "ntp.content_suggestions.notification.active";
-
-    private ContentSuggestionsNotifier() {} // Prevent instantiation
-
-    /**
-     * Records the reason why Content Suggestions notifications have been opted out.
-     * @see ContentSuggestionsNotificationOptOut;
-     */
-    public static void recordNotificationOptOut(@ContentSuggestionsNotificationOptOut int reason) {
-        nativeRecordNotificationOptOut(reason);
-    }
-
-    /**
-     * Records an action performed on a Content Suggestions notification.
-     * @see ContentSuggestionsNotificationAction;
-     */
-    public static void recordNotificationAction(@ContentSuggestionsNotificationAction int action) {
-        nativeRecordNotificationAction(action);
-    }
-
-    /**
-     * Opens the content suggestion when notification is tapped.
-     */
-    public static final class OpenUrlReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1);
-            String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA);
-            openUrl(intent.getData());
-            hideNotification(category, idWithinCategory, ContentSuggestionsNotificationAction.TAP);
-        }
-    }
-
-    /**
-     * Records dismissal when notification is swiped away.
-     */
-    public static final class DeleteReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1);
-            String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA);
-            if (removeActiveNotification(category, idWithinCategory)) {
-                recordCachedActionMetric(ContentSuggestionsNotificationAction.DISMISSAL);
-            }
-        }
-    }
-
-    /**
-     * Removes the notification after a timeout period.
-     */
-    public static final class TimeoutReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1);
-            String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA);
-            hideNotification(
-                    category, idWithinCategory, ContentSuggestionsNotificationAction.HIDE_DEADLINE);
-        }
-    }
-
-    private static void openUrl(Uri uri) {
-        Context context = ContextUtils.getApplicationContext();
-        Intent intent = new Intent()
-                                .setAction(Intent.ACTION_VIEW)
-                                .setData(uri)
-                                .setClass(context, ChromeLauncherActivity.class)
-                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                                .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName())
-                                .putExtra(ShortcutHelper.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, true);
-        IntentHandler.addTrustedIntentExtras(intent);
-        context.startActivity(intent);
-    }
-
-    @CalledByNative
-    private static boolean showNotification(int category, String idWithinCategory, String url,
-            String title, String text, Bitmap image, long timeoutAtMillis, int priority) {
-        if (findActiveNotification(category, idWithinCategory) != null) return false;
-
-        // Post notification.
-        Context context = ContextUtils.getApplicationContext();
-        NotificationManagerProxy manager = new NotificationManagerProxyImpl(context);
-
-        int nextId = nextNotificationId();
-        Uri uri = Uri.parse(url);
-        PendingIntentProvider contentIntent = PendingIntentProvider.getBroadcast(context, 0,
-                new Intent(context, OpenUrlReceiver.class)
-                        .setData(uri)
-                        .putExtra(NOTIFICATION_CATEGORY_EXTRA, category)
-                        .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory),
-                0);
-        PendingIntentProvider deleteIntent = PendingIntentProvider.getBroadcast(context, 0,
-                new Intent(context, DeleteReceiver.class)
-                        .setData(uri)
-                        .putExtra(NOTIFICATION_CATEGORY_EXTRA, category)
-                        .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory),
-                0);
-        ChromeNotificationBuilder builder =
-                NotificationBuilderFactory
-                        .createChromeNotificationBuilder(true /* preferCompat */,
-                                ChannelDefinitions.ChannelId.CONTENT_SUGGESTIONS,
-                                null /* remoteAppPackageName */,
-                                new NotificationMetadata(
-                                        NotificationUmaTracker.SystemNotificationType
-                                                .CONTENT_SUGGESTION,
-                                        NOTIFICATION_TAG, nextId))
-                        .setContentIntent(contentIntent)
-                        .setDeleteIntent(deleteIntent)
-                        .setContentTitle(title)
-                        .setContentText(text)
-                        .setGroup(NOTIFICATION_TAG)
-                        .setPriorityBeforeO(priority)
-                        .setLargeIcon(image)
-                        .setSmallIcon(R.drawable.ic_chrome);
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-            PendingIntentProvider settingsIntent = PendingIntentProvider.getActivity(context, 0,
-                    PreferencesLauncher.createIntentForSettingsPage(
-                            context, NotificationsPreferences.class.getName()),
-                    0);
-            builder.addAction(R.drawable.settings_cog, context.getString(R.string.preferences),
-                    settingsIntent, NotificationUmaTracker.ActionType.CONTENT_SUGGESTION_SETTINGS);
-        }
-        if (priority >= 0) builder.setDefaults(Notification.DEFAULT_ALL);
-        ChromeNotification notification = builder.buildChromeNotification();
-
-        manager.notify(notification);
-        NotificationUmaTracker.getInstance().onNotificationShown(
-                NotificationUmaTracker.SystemNotificationType.CONTENT_SUGGESTION,
-                notification.getNotification());
-
-        addActiveNotification(new ActiveNotification(nextId, category, idWithinCategory, uri));
-
-        // Set timeout.
-        if (timeoutAtMillis != Long.MAX_VALUE) {
-            AlarmManager alarmManager =
-                    (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-            Intent timeoutIntent =
-                    new Intent(context, TimeoutReceiver.class)
-                            .setData(Uri.parse(url))
-                            .putExtra(NOTIFICATION_ID_EXTRA, nextId)
-                            .putExtra(NOTIFICATION_CATEGORY_EXTRA, category)
-                            .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory);
-            alarmManager.set(AlarmManager.RTC, timeoutAtMillis,
-                    PendingIntent.getBroadcast(
-                            context, 0, timeoutIntent, PendingIntent.FLAG_UPDATE_CURRENT));
-        }
-        return true;
-    }
-
-    /**
-     * Hides a notification and records an action to the Actions histogram.
-     *
-     * If the notification is not actually visible, then no action will be taken, and the action
-     * will not be recorded.
-     */
-    @CalledByNative
-    private static void hideNotification(int category, String idWithinCategory, int why) {
-        ActiveNotification activeNotification = findActiveNotification(category, idWithinCategory);
-        if (!removeActiveNotification(category, idWithinCategory)) return;
-
-        Context context = ContextUtils.getApplicationContext();
-        NotificationManager manager =
-                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        manager.cancel(NOTIFICATION_TAG, activeNotification.mId);
-        recordCachedActionMetric(why);
-    }
-
-    @CalledByNative
-    private static void hideAllNotifications(int why) {
-        Context context = ContextUtils.getApplicationContext();
-        NotificationManager manager =
-                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        for (ActiveNotification activeNotification : getActiveNotifications()) {
-            manager.cancel(NOTIFICATION_TAG, activeNotification.mId);
-            if (removeActiveNotification(
-                        activeNotification.mCategory, activeNotification.mIdWithinCategory)) {
-                recordCachedActionMetric(why);
-            }
-        }
-        assert getActiveNotifications().isEmpty();
-    }
-
-    private static class ActiveNotification {
-        final int mId;
-        final int mCategory;
-        final String mIdWithinCategory;
-        final Uri mUri;
-
-        ActiveNotification(int id, int category, String idWithinCategory, Uri uri) {
-            mId = id;
-            mCategory = category;
-            mIdWithinCategory = idWithinCategory;
-            mUri = uri;
-        }
-
-        /** Parses the fields out of a chrome://content-suggestions-notification URI */
-        static ActiveNotification fromUri(Uri notificationUri) {
-            assert notificationUri.getScheme().equals("chrome");
-            assert notificationUri.getAuthority().equals("content-suggestions-notification");
-            assert notificationUri.getQueryParameter("id") != null;
-            assert notificationUri.getQueryParameter("category") != null;
-            assert notificationUri.getQueryParameter("idWithinCategory") != null;
-            assert notificationUri.getQueryParameter("uri") != null;
-
-            return new ActiveNotification(Integer.parseInt(notificationUri.getQueryParameter("id")),
-                    Integer.parseInt(notificationUri.getQueryParameter("category")),
-                    notificationUri.getQueryParameter("idWithinCategory"),
-                    Uri.parse(notificationUri.getQueryParameter("uri")));
-        }
-
-        /** Serializes the fields to a chrome://content-suggestions-notification URI */
-        Uri toUri() {
-            return new Uri.Builder()
-                    .scheme("chrome")
-                    .authority("content-suggestions-notification")
-                    .appendQueryParameter("id", Integer.toString(mId))
-                    .appendQueryParameter("category", Integer.toString(mCategory))
-                    .appendQueryParameter("idWithinCategory", mIdWithinCategory)
-                    .appendQueryParameter("uri", mUri.toString())
-                    .build();
-        }
-    }
-
-    /** Returns a mutable copy of the named pref. Never returns null. */
-    private static Set<String> getMutableStringSetPreference(
-            SharedPreferences prefs, String prefName) {
-        Set<String> prefValue = prefs.getStringSet(prefName, null);
-        if (prefValue == null) {
-            return new HashSet<String>();
-        }
-        return new HashSet<String>(prefValue);
-    }
-
-    /** Adds notification to the "active" set. */
-    private static void addActiveNotification(ActiveNotification notification) {
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        Set<String> activeNotifications =
-                getMutableStringSetPreference(prefs, PREF_ACTIVE_NOTIFICATIONS);
-        activeNotifications.add(notification.toUri().toString());
-        prefs.edit().putStringSet(PREF_ACTIVE_NOTIFICATIONS, activeNotifications).apply();
-    }
-
-    /** Removes notification from the "active" set. Returns false if it wasn't there. */
-    private static boolean removeActiveNotification(int category, String idWithinCategory) {
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        ActiveNotification notification = findActiveNotification(category, idWithinCategory);
-        if (notification == null) return false;
-
-        Set<String> activeNotifications =
-                getMutableStringSetPreference(prefs, PREF_ACTIVE_NOTIFICATIONS);
-        boolean result = activeNotifications.remove(notification.toUri().toString());
-        prefs.edit().putStringSet(PREF_ACTIVE_NOTIFICATIONS, activeNotifications).apply();
-        return result;
-    }
-
-    private static Collection<ActiveNotification> getActiveNotifications() {
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null);
-        if (activeNotifications == null) return Collections.emptySet();
-
-        Set<ActiveNotification> result = new HashSet<ActiveNotification>();
-        for (String serialized : activeNotifications) {
-            Uri notificationUri = Uri.parse(serialized);
-            ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri);
-            if (activeNotification != null) result.add(activeNotification);
-        }
-        return result;
-    }
-
-    /** Returns an ActiveNotification if a corresponding one is found, otherwise null. */
-    private static ActiveNotification findActiveNotification(
-            int category, String idWithinCategory) {
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null);
-        if (activeNotifications == null) return null;
-
-        for (String serialized : activeNotifications) {
-            Uri notificationUri = Uri.parse(serialized);
-            ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri);
-            if ((activeNotification != null) && (activeNotification.mCategory == category)
-                    && (activeNotification.mIdWithinCategory.equals(idWithinCategory))) {
-                return activeNotification;
-            }
-        }
-        return null;
-    }
-
-    /** Returns a non-negative integer greater than any active notification's notification ID. */
-    private static int nextNotificationId() {
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null);
-        if (activeNotifications == null) return 0;
-
-        int nextId = 0;
-        for (String serialized : activeNotifications) {
-            Uri notificationUri = Uri.parse(serialized);
-            ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri);
-            if ((activeNotification != null) && (activeNotification.mId >= nextId)) {
-                nextId = activeNotification.mId + 1;
-            }
-        }
-        return nextId;
-    }
-
-    private static String cachedMetricNameForAction(
-            @ContentSuggestionsNotificationAction int action) {
-        switch (action) {
-            case ContentSuggestionsNotificationAction.TAP:
-                return PREF_CACHED_ACTION_TAP;
-            case ContentSuggestionsNotificationAction.DISMISSAL:
-                return PREF_CACHED_ACTION_DISMISSAL;
-            case ContentSuggestionsNotificationAction.HIDE_DEADLINE:
-                return PREF_CACHED_ACTION_HIDE_DEADLINE;
-            case ContentSuggestionsNotificationAction.HIDE_EXPIRY:
-                return PREF_CACHED_ACTION_HIDE_EXPIRY;
-            case ContentSuggestionsNotificationAction.HIDE_FRONTMOST:
-                return PREF_CACHED_ACTION_HIDE_FRONTMOST;
-            case ContentSuggestionsNotificationAction.HIDE_DISABLED:
-                return PREF_CACHED_ACTION_HIDE_DISABLED;
-            case ContentSuggestionsNotificationAction.HIDE_SHUTDOWN:
-                return PREF_CACHED_ACTION_HIDE_SHUTDOWN;
-        }
-        return "";
-    }
-
-    /**
-     * Records that an action was performed on a notification.
-     *
-     * Also tracks the number of consecutively-ignored notifications, resetting it on a tap or
-     * otherwise incrementing it.
-     *
-     * This method may be called when the native library is not loaded. If it is loaded, the metrics
-     * will immediately be sent to C++. If not, it will cache them for a later call to
-     * flushCachedMetrics().
-     *
-     * @param action The action to update the pref for.
-     */
-    private static void recordCachedActionMetric(@ContentSuggestionsNotificationAction int action) {
-        String prefName = cachedMetricNameForAction(action);
-        assert !prefName.isEmpty();
-
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        int currentValue = prefs.getInt(prefName, 0);
-
-        int consecutiveIgnored = prefs.getInt(PREF_CACHED_CONSECUTIVE_IGNORED, 0);
-        switch (action) {
-            case ContentSuggestionsNotificationAction.TAP:
-                consecutiveIgnored = 0;
-                break;
-            case ContentSuggestionsNotificationAction.DISMISSAL:
-            case ContentSuggestionsNotificationAction.HIDE_DEADLINE:
-            case ContentSuggestionsNotificationAction.HIDE_EXPIRY:
-                ++consecutiveIgnored;
-                break;
-            case ContentSuggestionsNotificationAction.HIDE_FRONTMOST:
-            case ContentSuggestionsNotificationAction.HIDE_DISABLED:
-            case ContentSuggestionsNotificationAction.HIDE_SHUTDOWN:
-                break; // no change
-        }
-
-        prefs.edit()
-                .putInt(prefName, currentValue + 1)
-                .putInt(PREF_CACHED_CONSECUTIVE_IGNORED, consecutiveIgnored)
-                .apply();
-
-        flushCachedMetrics();
-    }
-
-    /**
-     * Registers the Content Suggestions notification channel on Android O.
-     *
-     * <p>The registration state is tracked in a preference, as a boolean. If the pref is false or
-     * missing, the channel will be (re-)registered.
-     *
-     * <p>May be called on any Android version; before Android O, it is a no-op.
-     *
-     * <p>Returns true if the channel was registered, false if not (pre-O, or already registered).
-     *
-     * @param enabled If false, the channel is created in a disabled state.
-     */
-    @CalledByNative
-    private static boolean registerChannel(boolean enabled) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false;
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        if (prefs.getBoolean(PREF_CHANNEL_CREATED, false)) return false;
-
-        ChannelsInitializer initializer = new ChannelsInitializer(
-                new NotificationManagerProxyImpl(ContextUtils.getApplicationContext()),
-                ContextUtils.getApplicationContext().getResources());
-        if (enabled) {
-            initializer.ensureInitialized(ChannelDefinitions.ChannelId.CONTENT_SUGGESTIONS);
-        } else {
-            initializer.ensureInitializedAndDisabled(
-                    ChannelDefinitions.ChannelId.CONTENT_SUGGESTIONS);
-        }
-        prefs.edit().putBoolean(PREF_CHANNEL_CREATED, true).apply();
-        return true;
-    }
-
-    /**
-     * Unregisters the Content Suggestions notification channel on Android O.
-     *
-     * <p>May be called on any Android version; before Android O, it is a no-op.
-     */
-    @CalledByNative
-    private static void unregisterChannel() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        if (!prefs.getBoolean(PREF_CHANNEL_CREATED, false)) return;
-
-        NotificationManagerProxy manager =
-                new NotificationManagerProxyImpl(ContextUtils.getApplicationContext());
-        manager.deleteNotificationChannel(ChannelDefinitions.ChannelId.CONTENT_SUGGESTIONS);
-        prefs.edit().remove(PREF_CHANNEL_CREATED).apply();
-    }
-
-    /**
-     * Invokes nativeReceiveFlushedMetrics() with cached metrics and resets them.
-     *
-     * It may be called from either native or Java code. If the browser has not been started-up, or
-     * startup has not completed (as when the native component flushes metrics during creation of
-     * the keyed service) the flush will be deferred until startup is complete.
-     */
-    @CalledByNative
-    private static void flushCachedMetrics() {
-        BrowserStartupController browserStartup =
-                BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER);
-        if (!browserStartup.isStartupSuccessfullyCompleted()) {
-            browserStartup.addStartupCompletedObserver(new StartupCallback() {
-                @Override
-                public void onSuccess() {
-                    flushCachedMetrics();
-                }
-                @Override
-                public void onFailure() {}
-            });
-            return;
-        }
-
-        SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
-        int tapCount = prefs.getInt(PREF_CACHED_ACTION_TAP, 0);
-        int dismissalCount = prefs.getInt(PREF_CACHED_ACTION_DISMISSAL, 0);
-        int hideDeadlineCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_DEADLINE, 0);
-        int hideExpiryCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_EXPIRY, 0);
-        int hideFrontmostCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_FRONTMOST, 0);
-        int hideDisabledCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_DISABLED, 0);
-        int hideShutdownCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_SHUTDOWN, 0);
-        int consecutiveIgnored = prefs.getInt(PREF_CACHED_CONSECUTIVE_IGNORED, 0);
-
-        if (tapCount > 0 || dismissalCount > 0 || hideDeadlineCount > 0 || hideExpiryCount > 0
-                || hideFrontmostCount > 0 || hideDisabledCount > 0 || hideShutdownCount > 0) {
-            nativeReceiveFlushedMetrics(tapCount, dismissalCount, hideDeadlineCount,
-                    hideExpiryCount, hideFrontmostCount, hideDisabledCount, hideShutdownCount,
-                    consecutiveIgnored);
-            prefs.edit()
-                    .remove(PREF_CACHED_ACTION_TAP)
-                    .remove(PREF_CACHED_ACTION_DISMISSAL)
-                    .remove(PREF_CACHED_ACTION_HIDE_DEADLINE)
-                    .remove(PREF_CACHED_ACTION_HIDE_EXPIRY)
-                    .remove(PREF_CACHED_ACTION_HIDE_FRONTMOST)
-                    .remove(PREF_CACHED_ACTION_HIDE_DISABLED)
-                    .remove(PREF_CACHED_ACTION_HIDE_SHUTDOWN)
-                    .remove(PREF_CACHED_CONSECUTIVE_IGNORED)
-                    .apply();
-        }
-    }
-
-    private static native void nativeReceiveFlushedMetrics(int tapCount, int dismissalCount,
-            int hideDeadlineCount, int hideExpiryCount, int hideFrontmostCount,
-            int hideDisabledCount, int hideShutdownCount, int consecutiveIgnored);
-    private static native void nativeRecordNotificationOptOut(
-            @ContentSuggestionsNotificationOptOut int reason);
-    private static native void nativeRecordNotificationAction(
-            @ContentSuggestionsNotificationAction int action);
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
index 0f79667..b4a1f83 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -84,14 +84,6 @@
         return nativeAreRemoteSuggestionsEnabled(mNativeSnippetsBridge);
     }
 
-    public static void setContentSuggestionsNotificationsEnabled(boolean enabled) {
-        nativeSetContentSuggestionsNotificationsEnabled(enabled);
-    }
-
-    public static boolean areContentSuggestionsNotificationsEnabled() {
-        return nativeAreContentSuggestionsNotificationsEnabled();
-    }
-
     @Override
     public void fetchRemoteSuggestions() {
         assert mNativeSnippetsBridge != 0;
@@ -250,8 +242,6 @@
     private static native void nativeRemoteSuggestionsSchedulerOnPersistentSchedulerWakeUp();
     private static native void nativeRemoteSuggestionsSchedulerOnBrowserUpgraded();
     private native boolean nativeAreRemoteSuggestionsEnabled(long nativeNTPSnippetsBridge);
-    private static native void nativeSetContentSuggestionsNotificationsEnabled(boolean enabled);
-    private static native boolean nativeAreContentSuggestionsNotificationsEnabled();
     private native int[] nativeGetCategories(long nativeNTPSnippetsBridge);
     private native int nativeGetCategoryStatus(long nativeNTPSnippetsBridge, int category);
     private native SuggestionsCategoryInfo nativeGetCategoryInfo(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java
index 04a6e28..e2b7cea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java
@@ -142,16 +142,14 @@
                 // isn't triggered.
                 return true;
             });
-        } else if (!ChromeFeatureList.isEnabled(
-                           ChromeFeatureList.CONTENT_SUGGESTIONS_NOTIFICATIONS)) {
-            // The Notifications Preferences page currently only contains the Content Suggestions
-            // Notifications setting and a link to per-website notification settings. The latter can
-            // be access through Site Settings, so if the Content Suggestions Notifications feature
-            // isn't enabled we don't show the Notifications Preferences page.
+        } else {
+            // Since the Content Suggestions Notification feature has been removed, the
+            // Notifications Preferences page only contains a link to per-website notification
+            // settings, which can be access through Site Settings, so don't show the Notifications
+            // Preferences page.
 
-            // This checks whether the Content Suggestions Notifications *feature* is enabled on the
-            // user's device, not whether the user has Content Suggestions Notifications themselves
-            // enabled (which is what the user can toggle on the Notifications Preferences page).
+            // TODO(crbug.com/944912): Have the Offline Pages Prefetch Notifier start using the pref
+            // that can be set on this page, then re-enable.
             getPreferenceScreen().removePreference(findPreference(PREF_NOTIFICATIONS));
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java
index e97b22a7..d237ce2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/NotificationsPreferences.java
@@ -48,11 +48,12 @@
             mSnippetsBridge = new SnippetsBridge(Profile.getLastUsedProfile());
 
             mSuggestionsPref = (ChromeSwitchPreference) findPreference(PREF_SUGGESTIONS);
-            mSuggestionsPref.setOnPreferenceChangeListener((Preference preference,
-                                                                   Object newValue) -> {
-                SnippetsBridge.setContentSuggestionsNotificationsEnabled((boolean) newValue);
-                return true;
-            });
+            mSuggestionsPref.setOnPreferenceChangeListener(
+                    (Preference preference, Object newValue) -> {
+                        PrefServiceBridge.getInstance().setBoolean(
+                                Pref.CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED, (boolean) newValue);
+                        return true;
+                    });
         } else {
             // This preference is not applicable, does not currently affect Feed.
             getPreferenceScreen().removePreference(findPreference(PREF_SUGGESTIONS));
@@ -86,7 +87,8 @@
             boolean suggestionsEnabled =
                     mSnippetsBridge != null && mSnippetsBridge.areRemoteSuggestionsEnabled();
             mSuggestionsPref.setChecked(suggestionsEnabled
-                    && SnippetsBridge.areContentSuggestionsNotificationsEnabled());
+                    && PrefServiceBridge.getInstance().getBoolean(
+                            Pref.CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED));
             mSuggestionsPref.setEnabled(suggestionsEnabled);
             mSuggestionsPref.setSummary(suggestionsEnabled
                             ? R.string.notifications_content_suggestions_summary
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
index 0ac6bc5..107f25aa0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDataCache.java
@@ -25,7 +25,6 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.profiles.ProfileDownloader;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.ProfileDataSource;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/profiles/ProfileDownloader.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
similarity index 90%
rename from chrome/android/java/src/org/chromium/chrome/browser/profiles/ProfileDownloader.java
rename to chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
index 746492b..8b948a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/profiles/ProfileDownloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ProfileDownloader.java
@@ -2,7 +2,7 @@
 // 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.profiles;
+package org.chromium.chrome.browser.signin;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -10,7 +10,7 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
-import org.chromium.chrome.browser.signin.IdentityServicesProvider;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.components.signin.AccountTrackerService;
 
 import java.util.ArrayList;
@@ -34,8 +34,8 @@
          * @param givenName A given name.
          * @param bitmap A user picture.
          */
-        void onProfileDownloaded(String accountId, String fullName, String givenName,
-                Bitmap bitmap);
+        void onProfileDownloaded(
+                String accountId, String fullName, String givenName, Bitmap bitmap);
     }
 
     /**
@@ -55,10 +55,10 @@
     }
 
     /**
-    * Private class to pend profile download requests when system accounts have not been seeded into
-    * AccountTrackerService. It listens onSystemAccountsSeedingComplete to finish pending requests
-    * and onSystemAccountsChanged to clear outdated pending requests.
-    */
+     * Private class to pend profile download requests when system accounts have not been seeded
+     * into AccountTrackerService. It listens onSystemAccountsSeedingComplete to finish pending
+     * requests and onSystemAccountsChanged to clear outdated pending requests.
+     */
     private static class PendingProfileDownloads
             implements AccountTrackerService.OnSystemAccountsSeededListener {
         private static PendingProfileDownloads sPendingProfileDownloads;
@@ -131,8 +131,8 @@
     }
 
     @CalledByNative
-    private static void onProfileDownloadSuccess(String accountId, String fullName,
-            String givenName, Bitmap bitmap) {
+    private static void onProfileDownloadSuccess(
+            String accountId, String fullName, String givenName, Bitmap bitmap) {
         ThreadUtils.assertOnUiThread();
         for (Observer observer : sObservers) {
             observer.onProfileDownloaded(accountId, fullName, givenName, bitmap);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/NotificationsPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/NotificationsPreferencesTest.java
index 6ce84b8..9548c802 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/NotificationsPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/NotificationsPreferencesTest.java
@@ -23,7 +23,6 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.ContentSettingsType;
-import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
 import org.chromium.chrome.browser.preferences.website.ContentSettingsResources;
 import org.chromium.chrome.browser.preferences.website.SingleCategoryPreferences;
 import org.chromium.chrome.browser.preferences.website.SiteSettingsCategory;
@@ -71,20 +70,23 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             // Make sure the toggle reflects the state correctly.
             boolean initiallyChecked = toggle.isChecked();
-            Assert.assertEquals(
-                    toggle.isChecked(), SnippetsBridge.areContentSuggestionsNotificationsEnabled());
+            Assert.assertEquals(toggle.isChecked(),
+                    PrefServiceBridge.getInstance().getBoolean(
+                            Pref.CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED));
 
             // Make sure we can change the state.
             PreferencesTest.clickPreference(fragment, toggle);
             Assert.assertEquals(toggle.isChecked(), !initiallyChecked);
-            Assert.assertEquals(
-                    toggle.isChecked(), SnippetsBridge.areContentSuggestionsNotificationsEnabled());
+            Assert.assertEquals(toggle.isChecked(),
+                    PrefServiceBridge.getInstance().getBoolean(
+                            Pref.CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED));
 
             // Make sure we can change it back.
             PreferencesTest.clickPreference(fragment, toggle);
             Assert.assertEquals(toggle.isChecked(), initiallyChecked);
-            Assert.assertEquals(
-                    toggle.isChecked(), SnippetsBridge.areContentSuggestionsNotificationsEnabled());
+            Assert.assertEquals(toggle.isChecked(),
+                    PrefServiceBridge.getInstance().getBoolean(
+                            Pref.CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED));
 
             // Click it one last time so we're in a toggled state for the UI Capture.
             PreferencesTest.clickPreference(fragment, toggle);
diff --git a/chrome/android/public/DEPS b/chrome/android/public/DEPS
index f26c162c..c2c3e18 100644
--- a/chrome/android/public/DEPS
+++ b/chrome/android/public/DEPS
@@ -1,4 +1,7 @@
 include_rules = [
   "-chrome/android/java",
   "-chrome/android/features",
+
+  "-content/public/android",
+  "+content/public/android/java/src/org/chromium/content_public",
 ]
diff --git a/chrome/android/public/crypto/BUILD.gn b/chrome/android/public/crypto/BUILD.gn
new file mode 100644
index 0000000..6edcb799
--- /dev/null
+++ b/chrome/android/public/crypto/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+android_library("java") {
+  deps = [
+    "//base:base_java",
+    "//content/public/android:content_java",
+  ]
+  java_files = [
+    "java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java",
+    "java/src/org/chromium/chrome/browser/crypto/CipherFactory.java",
+  ]
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crypto/OWNERS b/chrome/android/public/crypto/OWNERS
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/crypto/OWNERS
rename to chrome/android/public/crypto/OWNERS
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java b/chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java
rename to chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/ByteArrayGenerator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java b/chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
rename to chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
index f976d9b..7d0e501 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
+++ b/chrome/android/public/crypto/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
@@ -318,7 +318,6 @@
         mObservers.removeObserver(observer);
     }
 
-
     private void notifyCipherDataGenerated() {
         for (CipherDataObserver observer : mObservers) {
             observer.onCipherDataGenerated();
diff --git a/chrome/android/public/profiles/BUILD.gn b/chrome/android/public/profiles/BUILD.gn
new file mode 100644
index 0000000..4bd2d1f
--- /dev/null
+++ b/chrome/android/public/profiles/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+# Keep profiles and cookies in the same target due to bi-directional dependency
+# between Profile and CookiesFetcher.
+android_library("java") {
+  deps = [
+    "//base:base_java",
+    "//chrome/android/public/crypto:java",
+    "//content/public/android:content_java",
+  ]
+  java_files = [
+    "java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
+    "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
+    "java/src/org/chromium/chrome/browser/profiles/Profile.java",
+    "java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
+  ]
+}
+
+generate_jni("jni_headers") {
+  sources = [
+    "java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
+    "java/src/org/chromium/chrome/browser/profiles/Profile.java",
+    "java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java",
+  ]
+  jni_package = "chrome"
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java
rename to chrome/android/public/profiles/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java
rename to chrome/android/public/profiles/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java
index 6124ff8..274aa8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java
+++ b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java
@@ -127,8 +127,7 @@
                             cookie.getSameSite(), cookie.getPriority());
                 }
             }
-        }
-                .executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
     }
 
     /**
@@ -197,8 +196,7 @@
             }
 
             ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
-            CipherOutputStream cipherOut =
-                    new CipherOutputStream(byteOut, cipher);
+            CipherOutputStream cipherOut = new CipherOutputStream(byteOut, cipher);
             out = new DataOutputStream(cipherOut);
             CanonicalCookie.saveListToStream(out, cookies);
             out.close();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/profiles/Profile.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
rename to chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
index 7d227d5..092870f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
+++ b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/Profile.java
@@ -14,7 +14,6 @@
  * Wrapper that allows passing a Profile reference around in the Java layer.
  */
 public class Profile {
-
     /** Whether this wrapper corresponds to an off the record Profile. */
     private final boolean mIsOffTheRecord;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java b/chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java
rename to chrome/android/public/profiles/java/src/org/chromium/chrome/browser/profiles/ProfileManagerUtils.java
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 8995da7..5d7cfe1 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
@@ -134,6 +134,7 @@
     public void onLayoutCompleted(RecyclerView.State state) {
         super.onLayoutCompleted(state);
         if (mShouldFocusAfterLayout) {
+            mShouldFocusAfterLayout = false;
             mFocusView.requestFocus();
         }
     }
@@ -258,7 +259,7 @@
     }
 
     boolean focusCenterItem(int direction, Rect previouslyFocusedRect) {
-        if (mRecyclerView.isComputingLayout() || mFocusView == null) {
+        if (mRecyclerView == null || mRecyclerView.isComputingLayout() || mFocusView == null) {
             mShouldFocusAfterLayout = true;
             return true;
         }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
index 82d945a..8a205ebd 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
@@ -12,6 +12,7 @@
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
+import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ActionNames;
 import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
 import org.chromium.chrome.touchless.R;
 import org.chromium.ui.modaldialog.ModalDialogManager;
@@ -106,6 +107,10 @@
     private PropertyModel buildMenuModel(Context context, String title, PropertyModel[] menuItems) {
         PropertyModel.Builder builder =
                 new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS);
+        ActionNames names = new ActionNames();
+        names.cancel = context.getResources().getString(org.chromium.chrome.R.string.cancel);
+        names.select = context.getResources().getString(org.chromium.chrome.R.string.select);
+        names.alt = "";
         builder.with(TouchlessDialogProperties.IS_FULLSCREEN, true)
                 .with(ModalDialogProperties.CONTROLLER,
                         new ModalDialogProperties.Controller() {
@@ -115,11 +120,7 @@
                             @Override
                             public void onDismiss(PropertyModel model, int dismissalCause) {}
                         })
-                .with(TouchlessDialogProperties.ALT_NAME, "")
-                .with(TouchlessDialogProperties.SELECT_NAME, context.getResources(),
-                        org.chromium.chrome.R.string.select)
-                .with(TouchlessDialogProperties.CANCEL_NAME, context.getResources(),
-                        org.chromium.chrome.R.string.cancel)
+                .with(TouchlessDialogProperties.ACTION_NAMES, names)
                 .with(TouchlessDialogProperties.CANCEL_ACTION, (v) -> closeTouchlessContextMenu())
                 .with(TouchlessDialogProperties.LIST_MODELS, menuItems)
                 .with(TouchlessDialogProperties.PRIORITY, TouchlessDialogProperties.Priority.HIGH);
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java
index 35db7aa..a4984ed 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java
@@ -60,6 +60,15 @@
     }
 
     /**
+     * Struct-like class for holding the Names for the dialog actions.
+     */
+    public static class ActionNames {
+        public String cancel;
+        public String select;
+        public String alt;
+    }
+
+    /**
      * Whether the dialog is fullscreen. If false there will be a gap at the top showing the content
      * behind the dialog.
      */
@@ -72,22 +81,14 @@
     public static final WritableObjectPropertyKey<PropertyModel[]> LIST_MODELS =
             new WritableObjectPropertyKey<>();
 
-    /** The cancel event's name. */
-    public static final WritableObjectPropertyKey<String> CANCEL_NAME =
+    /** The names for the Actions. */
+    public static final WritableObjectPropertyKey<ActionNames> ACTION_NAMES =
             new WritableObjectPropertyKey<>();
 
     /** What will happen when cancel is triggered. */
     public static final WritableObjectPropertyKey<OnClickListener> CANCEL_ACTION =
             new WritableObjectPropertyKey<>();
 
-    /** The name of the select action. */
-    public static final WritableObjectPropertyKey<String> SELECT_NAME =
-            new WritableObjectPropertyKey<>();
-
-    /** The alternative action's name. */
-    public static final WritableObjectPropertyKey<String> ALT_NAME =
-            new WritableObjectPropertyKey<>();
-
     /** What will happen when alternative action is triggered. */
     public static final WritableObjectPropertyKey<OnClickListener> ALT_ACTION =
             new WritableObjectPropertyKey<>();
@@ -95,11 +96,11 @@
     /** The priority for this set of properties. */
     public static final WritableIntPropertyKey PRIORITY = new WritableIntPropertyKey();
 
-    public static final PropertyKey[] MINIMAL_DIALOG_KEYS = {ModalDialogProperties.TITLE,
-            CANCEL_NAME, CANCEL_ACTION, SELECT_NAME, ALT_NAME, ALT_ACTION, PRIORITY};
+    public static final PropertyKey[] MINIMAL_DIALOG_KEYS = {
+            ModalDialogProperties.TITLE, ACTION_NAMES, CANCEL_ACTION, ALT_ACTION, PRIORITY};
 
     public static final PropertyKey[] ALL_DIALOG_KEYS =
             PropertyModel.concatKeys(ModalDialogProperties.ALL_KEYS,
-                    new PropertyKey[] {CANCEL_NAME, CANCEL_ACTION, SELECT_NAME, ALT_NAME,
-                            ALT_ACTION, PRIORITY, IS_FULLSCREEN, LIST_MODELS});
+                    new PropertyKey[] {ACTION_NAMES, CANCEL_ACTION, ALT_ACTION, PRIORITY,
+                            IS_FULLSCREEN, LIST_MODELS});
 }
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index ed2c1c2..2a72baa 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -460,6 +460,7 @@
       "//ash/components/tap_visualizer/public/mojom",
       "//chrome/browser/chromeos/kiosk_next_home/mojom",
       "//chromeos/assistant:buildflags",
+      "//chromeos/services/cellular_setup/public/mojom",
       "//chromeos/services/device_sync/public/cpp:manifest",
       "//chromeos/services/ime/public/mojom",
       "//chromeos/services/media_perception/public/mojom",
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index 3ef3265..ba4b3ef 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -49,6 +49,7 @@
     "+chrome/services/app_service",
     "+chromeos/assistant",
     "+chromeos/services/assistant",
+    "+chromeos/services/cellular_setup",
     "+chromeos/services/device_sync",
     "+chromeos/services/ime/public",
     "+chromeos/services/media_perception/public",
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index 97b37a5..b9f99bdf 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -52,6 +52,7 @@
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"  // nogncheck
 #include "chrome/browser/chromeos/kiosk_next_home/mojom/kiosk_next_home_interface_broker.mojom.h"  // nogncheck
 #include "chromeos/assistant/buildflags.h"  // nogncheck
+#include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.h"
 #include "chromeos/services/device_sync/public/cpp/manifest.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
 #include "chromeos/services/media_perception/public/mojom/media_perception.mojom.h"
@@ -203,6 +204,10 @@
             .RequireCapability(tap_visualizer::mojom::kServiceName,
                                tap_visualizer::mojom::kShowUiCapability)
             .ExposeInterfaceFilterCapability_Deprecated(
+                "navigation:frame", "cellular_setup",
+                service_manager::Manifest::InterfaceList<
+                    chromeos::cellular_setup::mojom::CellularSetup>())
+            .ExposeInterfaceFilterCapability_Deprecated(
                 "navigation:frame", "multidevice_setup",
                 service_manager::Manifest::InterfaceList<
                     chromeos::multidevice_setup::mojom::MultiDeviceSetup,
diff --git a/chrome/app/chrome_content_renderer_overlay_manifest.cc b/chrome/app/chrome_content_renderer_overlay_manifest.cc
index 8e4f7511..691e112 100644
--- a/chrome/app/chrome_content_renderer_overlay_manifest.cc
+++ b/chrome/app/chrome_content_renderer_overlay_manifest.cc
@@ -72,6 +72,8 @@
 #endif
                 subresource_filter::mojom::SubresourceFilterAgent>())
         .RequireInterfaceFilterCapability_Deprecated(
+            "content_browser", "navigation:frame", "cellular_setup")
+        .RequireInterfaceFilterCapability_Deprecated(
             "content_browser", "navigation:frame", "multidevice_setup")
         .Build()
   };
diff --git a/chrome/app/file_pre_reader_win.cc b/chrome/app/file_pre_reader_win.cc
index c62c8c0d..38f5439a 100644
--- a/chrome/app/file_pre_reader_win.cc
+++ b/chrome/app/file_pre_reader_win.cc
@@ -6,24 +6,66 @@
 
 #include <windows.h>
 
+#include <memoryapi.h>  // NOLINT(build/include_order)
+
 #include "base/files/file.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/win/windows_version.h"
 
 void PreReadFile(const base::FilePath& file_path) {
-  base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
-                                 base::File::FLAG_SEQUENTIAL_SCAN);
-  if (!file.IsValid())
-    return;
+  // On Win10 RS6 and higher with the increased prefetch limit we don't need
+  // to do in process prefetch. On OS releases Win8/Server 2012 to Win10 RS5
+  // use ::PrefetchVirtualMemory(). This is better than a simple data file
+  // read, more from a RAM perspective than CPU. This is because reading the
+  // file as data results in double mapping to Image/executable pages for all
+  // pages of code executed. On Win7 just do a simple file read as data.
 
-  // This could be replaced with ::PrefetchVirtualMemory once we drop support
-  // for Win7. The performance of ::PrefetchVirtualMemory is roughly equivalent
-  // to these buffered reads.
-  const DWORD kStepSize = 1024 * 1024;
-  char* buffer = reinterpret_cast<char*>(
-      ::VirtualAlloc(nullptr, kStepSize, MEM_COMMIT, PAGE_READWRITE));
-  if (!buffer)
-    return;
+  if (base::win::GetVersion() == base::win::Version::WIN7) {
+    // On Win7 read in the file as data since the OS doesn't have
+    // the support for better options.
 
-  while (file.ReadAtCurrentPos(buffer, kStepSize) > 0) {}
+    constexpr DWORD kStepSize = 1024 * 1024;
 
-  ::VirtualFree(buffer, 0, MEM_RELEASE);
+    base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                                   base::File::FLAG_SEQUENTIAL_SCAN);
+    if (!file.IsValid())
+      return;
+
+    char* buffer = reinterpret_cast<char*>(
+        ::VirtualAlloc(nullptr, kStepSize, MEM_COMMIT, PAGE_READWRITE));
+    if (!buffer)
+      return;
+
+    while (file.ReadAtCurrentPos(buffer, kStepSize) > 0) {
+    }
+
+    ::VirtualFree(buffer, 0, MEM_RELEASE);
+  } else {
+    // NB: Creating the file mapping before the ::LoadLibrary() of the file is
+    // more efficient memory wise, but we must be sure no other threads try to
+    // loadlibrary the file while we are doing the mapping and prefetching or
+    // the process will get a private copy of the DLL via COW.
+
+    base::MemoryMappedFile mapped_file;
+    if (mapped_file.Initialize(file_path,
+                               base::MemoryMappedFile::READ_CODE_IMAGE)) {
+      // RefSet data indicates we touch only the first half of the DLL
+      // so prefetch approximately the first half.
+
+      _WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(),
+                                                 mapped_file.length() / 2};
+
+      // ::PrefetchVirtualMemory() isn't available on Win7.
+      HMODULE kernel32_library = GetModuleHandleA("kernel32.dll");
+
+      auto prefetch_virtual_memory =
+          reinterpret_cast<decltype(&::PrefetchVirtualMemory)>(
+              GetProcAddress(kernel32_library, "PrefetchVirtualMemory"));
+
+      // NB: PrefetchVirtualMemory requires the file to be opened with
+      // only read access or it will fail.
+
+      (*prefetch_virtual_memory)(GetCurrentProcess(), 1, &address_range, 0);
+    }
+  }
 }
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 89da853..4f9c504 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1368,7 +1368,7 @@
       Add a printer manually
     </message>
     <message name="IDS_SETTINGS_PRINTING_CUPS_SELECT_MANUFACTURER_AND_MODEL_TITLE" desc="Text for the title of the dialog that is used to select a manufacturer and model from the drop down list.">
-      Select a printer manufacturer and model
+      Advanced printer configuration
     </message>
     <message name="IDS_SETTINGS_PRINTING_CUPS_PRINTER_DETAILS_TITLE" desc="Text for the title of the Printer Details subpage.">
       Printer details
@@ -1496,6 +1496,9 @@
     <message name="IDS_SETTINGS_PRINTING_CUPS_EDIT_PRINTER_CURRENT_PPD_MESSAGE" desc="Informational text displayed to the user if they have an existing custom PPD.">
       Current PPD file in use: <ph name="PPD_NAME">$1<ex>example.ppd.gz</ex></ph>
     </message>
+    <message name="IDS_SETTINGS_PRINTING_CUPS_MANUFACTURER_MODEL_ADDITIONAL_INFORMATION" desc="Informational text displayed to the user when the the user is doing advanced manual printer setup.">
+      <ph name="PRINTER_NAME">$1<ex>Printer</ex></ph> could not be configured automatically. Please specify advanced printer details.
+    </message>
   </if>
   <if expr="not chromeos">
     <message name="IDS_SETTINGS_PRINTING_LOCAL_PRINTERS_TITLE" desc="In Printing Settings, the title of local printers setting section on OS other than Chrome OS.">
diff --git a/chrome/app_shim/BUILD.gn b/chrome/app_shim/BUILD.gn
index 1d09936e..f651d88 100644
--- a/chrome/app_shim/BUILD.gn
+++ b/chrome/app_shim/BUILD.gn
@@ -25,12 +25,12 @@
     "//chrome/common",
     "//chrome/common:mojo_bindings",
     "//components/crash/content/app",
+    "//components/remote_cocoa/app_shim",
     "//content/public/browser",
     "//ipc",
     "//mojo/core/embedder",
     "//ui/accelerated_widget_mac",
     "//ui/base",
-    "//ui/views_bridge_mac",
   ]
 }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 733b7f7..7bb4368 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -937,8 +937,6 @@
     "notifications/platform_notification_service_impl.h",
     "notifications/system_notification_helper.cc",
     "notifications/system_notification_helper.h",
-    "ntp_snippets/content_suggestions_notifier_service_factory.cc",
-    "ntp_snippets/content_suggestions_notifier_service_factory.h",
     "ntp_snippets/content_suggestions_service_factory.cc",
     "ntp_snippets/content_suggestions_service_factory.h",
     "ntp_snippets/contextual_content_suggestions_service_factory.cc",
@@ -2422,12 +2420,6 @@
       "android/mojo/chrome_interface_registrar_android.h",
       "android/net/nqe/network_quality_provider.cc",
       "android/net/nqe/network_quality_provider.h",
-      "android/ntp/android_content_suggestions_notifier.cc",
-      "android/ntp/android_content_suggestions_notifier.h",
-      "android/ntp/content_suggestions_notifier.cc",
-      "android/ntp/content_suggestions_notifier.h",
-      "android/ntp/content_suggestions_notifier_service.cc",
-      "android/ntp/content_suggestions_notifier_service.h",
       "android/ntp/get_remote_suggestions_scheduler.cc",
       "android/ntp/get_remote_suggestions_scheduler.h",
       "android/ntp/most_visited_sites_bridge.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a30d3813..74a54e4 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -538,18 +538,6 @@
 #endif  // OS_CHROMEOS
 
 #if defined(OS_ANDROID)
-const FeatureEntry::FeatureParam kForceFetchedSuggestionsNotifications[] = {
-    {"force_fetched_suggestions_notifications", "true"},
-    {"enable_fetched_suggestions_notifications", "true"}};
-
-const FeatureEntry::FeatureVariation
-    kContentSuggestionsNotificationsFeatureVariations[] = {
-        {"(notify always, server side)", nullptr, 0, "3313312"},
-        {"(notify always, client side)", kForceFetchedSuggestionsNotifications,
-         base::size(kForceFetchedSuggestionsNotifications), nullptr}};
-#endif  // OS_ANDROID
-
-#if defined(OS_ANDROID)
 const FeatureEntry::FeatureParam
     kInterestFeedLargerImagesFeatureVariationConstant[] = {
         {"feed_ui_enabled", "true"}};
@@ -605,20 +593,6 @@
      switches::kForceDisplayColorProfile, "scrgb-linear"},
 };
 
-const FeatureEntry::Choice kAutoplayPolicyChoices[] = {
-    {flags_ui::kGenericExperimentChoiceDefault, "", ""},
-    {flag_descriptions::kAutoplayPolicyNoUserGestureRequired,
-     switches::kAutoplayPolicy,
-     switches::autoplay::kNoUserGestureRequiredPolicy},
-#if defined(OS_ANDROID)
-    {flag_descriptions::kAutoplayPolicyUserGestureRequired,
-     switches::kAutoplayPolicy, switches::autoplay::kUserGestureRequiredPolicy},
-#endif
-    {flag_descriptions::kAutoplayPolicyDocumentUserActivation,
-     switches::kAutoplayPolicy,
-     switches::autoplay::kDocumentUserActivationRequiredPolicy},
-};
-
 const FeatureEntry::Choice kForceEffectiveConnectionTypeChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kEffectiveConnectionTypeUnknownDescription,
@@ -1300,6 +1274,13 @@
     {"enable_messages_web_push", flag_descriptions::kEnableMessagesWebPushName,
      flag_descriptions::kEnableMessagesWebPushDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kEnableMessagesWebPush)},
+    {
+        "enable-background-blur",
+        flag_descriptions::kEnableBackgroundBlurName,
+        flag_descriptions::kEnableBackgroundBlurDescription,
+        kOsCrOS,
+        FEATURE_VALUE_TYPE(app_list_features::kEnableBackgroundBlur),
+    },
     {"enable-notification-indicator",
      flag_descriptions::kNotificationIndicatorName,
      flag_descriptions::kNotificationIndicatorDescription, kOsCrOS,
@@ -2039,14 +2020,6 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(ntp_snippets::kArticleSuggestionsFeature,
                                     kRemoteSuggestionsFeatureVariations,
                                     "NTPArticleSuggestions")},
-    {"enable-ntp-suggestions-notifications",
-     flag_descriptions::kEnableNtpSuggestionsNotificationsName,
-     flag_descriptions::kEnableNtpSuggestionsNotificationsDescription,
-     kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         ntp_snippets::kNotificationsFeature,
-         kContentSuggestionsNotificationsFeatureVariations,
-         "ContentSuggestionsNotifications")},
 #endif  // OS_ANDROID
     {"user-activation-v2", flag_descriptions::kUserActivationV2Name,
      flag_descriptions::kUserActivationV2Description, kOsAll,
@@ -2552,10 +2525,6 @@
      FEATURE_VALUE_TYPE(features::kWebNfc)},
 #endif
 
-    {"autoplay-policy", flag_descriptions::kAutoplayPolicyName,
-     flag_descriptions::kAutoplayPolicyDescription, kOsAll,
-     MULTI_VALUE_TYPE(kAutoplayPolicyChoices)},
-
     {"force-effective-connection-type",
      flag_descriptions::kForceEffectiveConnectionTypeName,
      flag_descriptions::kForceEffectiveConnectionTypeDescription, kOsAll,
@@ -3215,11 +3184,6 @@
      flag_descriptions::kEnableAccessibilityObjectModelDescription, kOsAll,
      SINGLE_VALUE_TYPE(switches::kEnableAccessibilityObjectModel)},
 
-    {"enable-autoplay-ignore-web-audio",
-     flag_descriptions::kEnableAutoplayIgnoreWebAudioName,
-     flag_descriptions::kEnableAutoplayIgnoreWebAudioDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(media::kAutoplayIgnoreWebAudio)},
-
 #if defined(OS_ANDROID)
     {"cct-module", flag_descriptions::kCCTModuleName,
      flag_descriptions::kCCTModuleDescription, kOsAndroid,
@@ -3383,11 +3347,6 @@
      kOsAll,
      FEATURE_VALUE_TYPE(features::kCompositorThreadedScrollbarScrolling)},
 
-    {"enable-autoplay-unified-sound-settings",
-     flag_descriptions::kEnableAutoplayUnifiedSoundSettingsName,
-     flag_descriptions::kEnableAutoplayUnifiedSoundSettingsDescription,
-     kOsDesktop, FEATURE_VALUE_TYPE(media::kAutoplayWhitelistSettings)},
-
 #if defined(OS_CHROMEOS)
     {"enable-chromeos-account-manager",
      flag_descriptions::kEnableChromeOsAccountManagerName,
@@ -3819,6 +3778,10 @@
      FEATURE_VALUE_TYPE(features::kMimeHandlerViewInCrossProcessFrame)},
 #endif
 
+    {"strict-origin-isolation", flag_descriptions::kStrictOriginIsolationName,
+     flag_descriptions::kStrictOriginIsolationDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kStrictOriginIsolation)},
+
     {"autofill-no-local-save-on-unmask-success",
      flag_descriptions::kAutofillNoLocalSaveOnUnmaskSuccessName,
      flag_descriptions::kAutofillNoLocalSaveOnUnmaskSuccessDescription, kOsAll,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 5c84557..e822721 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -188,7 +188,6 @@
     &payments::features::kWebPaymentsSingleAppUiSkip,
     &language::kExplicitLanguageAsk,
     &ntp_snippets::kArticleSuggestionsFeature,
-    &ntp_snippets::kNotificationsFeature,
     &ntp_tiles::kSiteExplorationUiFeature,
     &offline_pages::kOfflineIndicatorFeature,
     &offline_pages::kOfflineIndicatorAlwaysHttpProbeFeature,
diff --git a/chrome/browser/android/ntp/android_content_suggestions_notifier.cc b/chrome/browser/android/ntp/android_content_suggestions_notifier.cc
deleted file mode 100644
index 5c86b031..0000000
--- a/chrome/browser/android/ntp/android_content_suggestions_notifier.cc
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2016 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/ntp/android_content_suggestions_notifier.h"
-
-#include <jni.h>
-#include <limits>
-
-#include "base/android/jni_android.h"
-#include "base/android/jni_string.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/common/pref_names.h"
-#include "components/ntp_snippets/features.h"
-#include "components/prefs/pref_service.h"
-#include "jni/ContentSuggestionsNotifier_jni.h"
-#include "ui/gfx/android/java_bitmap.h"
-#include "ui/gfx/image/image.h"
-#include "ui/gfx/image/image_skia.h"
-
-using base::android::JavaParamRef;
-using ntp_snippets::ContentSuggestion;
-using ntp_snippets::kNotificationsFeature;
-using ntp_snippets::kNotificationsIgnoredLimitParam;
-using ntp_snippets::kNotificationsIgnoredDefaultLimit;
-
-AndroidContentSuggestionsNotifier::AndroidContentSuggestionsNotifier() =
-    default;
-
-bool AndroidContentSuggestionsNotifier::SendNotification(
-    const ContentSuggestion::ID& id,
-    const GURL& url,
-    const base::string16& title,
-    const base::string16& text,
-    const gfx::Image& image,
-    base::Time timeout_at,
-    int priority) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  SkBitmap skimage = image.AsImageSkia().GetRepresentation(1.0f).GetBitmap();
-  if (skimage.empty())
-    return false;
-
-  jlong timeout_at_millis = timeout_at.ToJavaTime();
-  if (timeout_at == base::Time::Max()) {
-    timeout_at_millis = std::numeric_limits<jlong>::max();
-  }
-
-  if (Java_ContentSuggestionsNotifier_showNotification(
-          env, id.category().id(),
-          base::android::ConvertUTF8ToJavaString(env, id.id_within_category()),
-          base::android::ConvertUTF8ToJavaString(env, url.spec()),
-          base::android::ConvertUTF16ToJavaString(env, title),
-          base::android::ConvertUTF16ToJavaString(env, text),
-          gfx::ConvertToJavaBitmap(&skimage), timeout_at_millis, priority)) {
-    DVLOG(1) << "Displayed notification for " << id;
-    return true;
-  } else {
-    DVLOG(1) << "Suppressed notification for " << url.spec();
-    return false;
-  }
-}
-
-void AndroidContentSuggestionsNotifier::HideNotification(
-    const ContentSuggestion::ID& id,
-    ContentSuggestionsNotificationAction why) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  Java_ContentSuggestionsNotifier_hideNotification(
-      env, id.category().id(),
-      base::android::ConvertUTF8ToJavaString(env, id.id_within_category()),
-      static_cast<int>(why));
-}
-
-void AndroidContentSuggestionsNotifier::HideAllNotifications(
-    ContentSuggestionsNotificationAction why) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  Java_ContentSuggestionsNotifier_hideAllNotifications(env,
-                                                       static_cast<int>(why));
-}
-
-void AndroidContentSuggestionsNotifier::FlushCachedMetrics() {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  Java_ContentSuggestionsNotifier_flushCachedMetrics(env);
-}
-
-bool AndroidContentSuggestionsNotifier::RegisterChannel(bool enabled) {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  return Java_ContentSuggestionsNotifier_registerChannel(env, enabled);
-}
-
-void AndroidContentSuggestionsNotifier::UnregisterChannel() {
-  JNIEnv* env = base::android::AttachCurrentThread();
-  return Java_ContentSuggestionsNotifier_unregisterChannel(env);
-}
-
-static void JNI_ContentSuggestionsNotifier_RecordNotificationOptOut(
-    JNIEnv* env,
-    jint reason) {
-  RecordContentSuggestionsNotificationOptOut(
-      static_cast<ContentSuggestionsNotificationOptOut>(reason));
-}
-
-static void JNI_ContentSuggestionsNotifier_RecordNotificationAction(
-    JNIEnv* env,
-    jint action) {
-  RecordContentSuggestionsNotificationAction(
-      static_cast<ContentSuggestionsNotificationAction>(action));
-}
-
-static void JNI_ContentSuggestionsNotifier_ReceiveFlushedMetrics(
-    JNIEnv* env,
-    jint tap_count,
-    jint dismissal_count,
-    jint hide_deadline_count,
-    jint hide_expiry_count,
-    jint hide_frontmost_count,
-    jint hide_disabled_count,
-    jint hide_shutdown_count,
-    jint consecutive_ignored) {
-  DVLOG(1) << "Flushing metrics: tap_count=" << tap_count
-           << "; dismissal_count=" << dismissal_count
-           << "; hide_deadline_count=" << hide_deadline_count
-           << "; hide_expiry_count=" << hide_expiry_count
-           << "; hide_frontmost_count=" << hide_frontmost_count
-           << "; hide_disabled_count=" << hide_disabled_count
-           << "; hide_shutdown_count=" << hide_shutdown_count
-           << "; consecutive_ignored=" << consecutive_ignored;
-  Profile* profile = ProfileManager::GetLastUsedProfile()->GetOriginalProfile();
-  PrefService* prefs = profile->GetPrefs();
-
-  for (int i = 0; i < tap_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::TAP);
-  }
-  for (int i = 0; i < dismissal_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::DISMISSAL);
-  }
-  for (int i = 0; i < hide_deadline_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::HIDE_DEADLINE);
-  }
-  for (int i = 0; i < hide_expiry_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::HIDE_EXPIRY);
-  }
-  for (int i = 0; i < hide_frontmost_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::HIDE_FRONTMOST);
-  }
-  for (int i = 0; i < hide_disabled_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::HIDE_DISABLED);
-  }
-  for (int i = 0; i < hide_shutdown_count; ++i) {
-    RecordContentSuggestionsNotificationAction(
-        ContentSuggestionsNotificationAction::HIDE_SHUTDOWN);
-  }
-
-  const bool was_enabled =
-      ContentSuggestionsNotifier::ShouldSendNotifications(prefs);
-  if (tap_count == 0) {
-    // There were no taps, consecutive_ignored has not been reset and continues
-    // from where it left off. If there was a tap, then Java has provided us
-    // with the number of ignored notifications since that point.
-    consecutive_ignored +=
-        prefs->GetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName);
-  }
-  prefs->SetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName,
-                    consecutive_ignored);
-  const bool is_enabled =
-      ContentSuggestionsNotifier::ShouldSendNotifications(prefs);
-  if (was_enabled && !is_enabled) {
-    RecordContentSuggestionsNotificationOptOut(
-        ContentSuggestionsNotificationOptOut::IMPLICIT);
-  }
-}
diff --git a/chrome/browser/android/ntp/android_content_suggestions_notifier.h b/chrome/browser/android/ntp/android_content_suggestions_notifier.h
deleted file mode 100644
index a04b236..0000000
--- a/chrome/browser/android/ntp/android_content_suggestions_notifier.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2016 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_NTP_ANDROID_CONTENT_SUGGESTIONS_NOTIFIER_H_
-#define CHROME_BROWSER_ANDROID_NTP_ANDROID_CONTENT_SUGGESTIONS_NOTIFIER_H_
-
-#include "base/macros.h"
-#include "base/strings/string16.h"
-#include "base/time/time.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier.h"
-#include "chrome/browser/ntp_snippets/ntp_snippets_metrics.h"
-#include "url/gurl.h"
-
-// Implements ContentSuggestionsNotifier methods with JNI calls.
-class AndroidContentSuggestionsNotifier : public ContentSuggestionsNotifier {
- public:
-  AndroidContentSuggestionsNotifier();
-
-  // ContentSuggestionsNotifier
-  bool SendNotification(const ntp_snippets::ContentSuggestion::ID& id,
-                        const GURL& url,
-                        const base::string16& title,
-                        const base::string16& text,
-                        const gfx::Image& image,
-                        base::Time timeout_at,
-                        int priority) override;
-  void HideNotification(const ntp_snippets::ContentSuggestion::ID& id,
-                        ContentSuggestionsNotificationAction why) override;
-  void HideAllNotifications(ContentSuggestionsNotificationAction why) override;
-  void FlushCachedMetrics() override;
-  bool RegisterChannel(bool enabled) override;
-  void UnregisterChannel() override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(AndroidContentSuggestionsNotifier);
-};
-
-#endif  // CHROME_BROWSER_ANDROID_NTP_ANDROID_CONTENT_SUGGESTIONS_NOTIFIER_H_
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier.cc b/chrome/browser/android/ntp/content_suggestions_notifier.cc
deleted file mode 100644
index c4bc3173..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2016 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/ntp/content_suggestions_notifier.h"
-
-#include <limits>
-
-#include "chrome/common/pref_names.h"
-#include "components/ntp_snippets/features.h"
-#include "components/ntp_snippets/pref_names.h"
-#include "components/prefs/pref_service.h"
-#include "components/variations/variations_associated_data.h"
-
-using ntp_snippets::kNotificationsFeature;
-using ntp_snippets::kNotificationsIgnoredLimitParam;
-using ntp_snippets::kNotificationsIgnoredDefaultLimit;
-
-namespace {
-
-// Whether auto opt out is enabled. Note that this does not disable collection
-// of data required for auto opt out. Auto opt out is currently disabled,
-// because notification settings page is shown when kNotificationsFeature is
-// enabled.
-const bool kEnableAutoOptOutDefault = false;
-const char kEnableAutoOptOutParamName[] = "enable_auto_opt_out";
-
-bool IsAutoOptOutEnabled() {
-  return variations::GetVariationParamByFeatureAsBool(
-      ntp_snippets::kNotificationsFeature, kEnableAutoOptOutParamName,
-      kEnableAutoOptOutDefault);
-}
-
-}  // namespace
-
-bool ContentSuggestionsNotifier::ShouldSendNotifications(PrefService* prefs) {
-  // Notifications are blocked when the suggested articles list is hidden.
-  if (!prefs->GetBoolean(ntp_snippets::prefs::kArticlesListVisible)) {
-    return false;
-  }
-
-  if (!prefs->GetBoolean(prefs::kContentSuggestionsNotificationsEnabled)) {
-    return false;
-  }
-
-  if (IsAutoOptOutEnabled()) {
-    int current =
-        prefs->GetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName);
-    int limit = variations::GetVariationParamByFeatureAsInt(
-        kNotificationsFeature, kNotificationsIgnoredLimitParam,
-        kNotificationsIgnoredDefaultLimit);
-    if (current >= limit) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-ContentSuggestionsNotifier::ContentSuggestionsNotifier() = default;
-ContentSuggestionsNotifier::~ContentSuggestionsNotifier() = default;
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier.h b/chrome/browser/android/ntp/content_suggestions_notifier.h
deleted file mode 100644
index 1207c84..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2016 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_NTP_CONTENT_SUGGESTIONS_NOTIFIER_H_
-#define CHROME_BROWSER_ANDROID_NTP_CONTENT_SUGGESTIONS_NOTIFIER_H_
-
-#include "base/macros.h"
-#include "base/strings/string16.h"
-#include "base/time/time.h"
-#include "chrome/browser/ntp_snippets/ntp_snippets_metrics.h"
-#include "components/ntp_snippets/content_suggestion.h"
-
-class GURL;
-class PrefService;
-
-namespace gfx {
-class Image;
-}  // namespace gfx
-
-class ContentSuggestionsNotifier {
- public:
-  ContentSuggestionsNotifier();
-  virtual ~ContentSuggestionsNotifier();
-
-  // Returns true if notifications should be sent.
-  //
-  // This function considers:
-  //   * If the user has disabled notifications through preferences.
-  //   * If the user has ignored enough consecutive notifications to treat that
-  //     as disabling notifications (if auto-opt-out is enabled).
-  //
-  // It does not consider:
-  //   * Whether the notifications feature is enabled. In this case, none of the
-  //     notifications machinery is instantiated to begin with.
-  //   * On Android O, if the notification channel is disabled.
-  static bool ShouldSendNotifications(PrefService* prefs);
-
-  virtual bool SendNotification(const ntp_snippets::ContentSuggestion::ID& id,
-                                const GURL& url,
-                                const base::string16& title,
-                                const base::string16& text,
-                                const gfx::Image& image,
-                                base::Time timeout_at,
-                                int priority) = 0;
-  virtual void HideNotification(const ntp_snippets::ContentSuggestion::ID& id,
-                                ContentSuggestionsNotificationAction why) = 0;
-  virtual void HideAllNotifications(
-      ContentSuggestionsNotificationAction why) = 0;
-
-  // Moves metrics tracked in Java into native histograms. Should be called when
-  // the native library starts up, to capture any actions that were taken since
-  // the last time it was running. (Harmless to call more often, though)
-  //
-  // Also updates the "consecutive ignored" preference, which is computed from
-  // the actions taken on notifications, and maybe the "opt outs" metric, which
-  // is computed in turn from that.
-  virtual void FlushCachedMetrics() = 0;
-
-  // Registers the notification channel on Android O. May be called regardless
-  // of Android version or registration state; it is a no-op before Android O,
-  // or if the channel is already registered. If |!enabled|, then the channel is
-  // disabled at creation. Returns true if the channel was created (false pre-O,
-  // or if it already existed).
-  virtual bool RegisterChannel(bool enabled) = 0;
-
-  // Unregisters the notification channel on Android O. May be called regardless
-  // of Android version or registration state; it is a no-op before Android O,
-  // or if the channel is not registered.
-  virtual void UnregisterChannel() = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ContentSuggestionsNotifier);
-};
-
-#endif  // CHROME_BROWSER_ANDROID_NTP_CONTENT_SUGGESTIONS_NOTIFIER_H_
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier_service.cc b/chrome/browser/android/ntp/content_suggestions_notifier_service.cc
deleted file mode 100644
index d21e6a6..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier_service.cc
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2016 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/ntp/content_suggestions_notifier_service.h"
-
-#include <algorithm>
-
-#include "base/android/application_status_listener.h"
-#include "base/bind.h"
-#include "base/strings/string_number_conversions.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier.h"
-#include "chrome/browser/notifications/notification_handler.h"
-#include "chrome/browser/ntp_snippets/ntp_snippets_metrics.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/common/url_constants.h"
-#include "components/ntp_snippets/content_suggestions_service.h"
-#include "components/ntp_snippets/features.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/pref_service.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/variations/variations_associated_data.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/gfx/image/image_skia_operations.h"
-
-using ntp_snippets::Category;
-using ntp_snippets::CategoryStatus;
-using ntp_snippets::ContentSuggestion;
-using ntp_snippets::ContentSuggestionsService;
-using ntp_snippets::KnownCategories;
-using ntp_snippets::kNotificationsDailyLimit;
-using ntp_snippets::kNotificationsDefaultDailyLimit;
-using ntp_snippets::kNotificationsDefaultPriority;
-using ntp_snippets::kNotificationsFeature;
-using ntp_snippets::kNotificationsKeepWhenFrontmostParam;
-using ntp_snippets::kNotificationsOpenToNTPParam;
-using ntp_snippets::kNotificationsPriorityParam;
-using ntp_snippets::kNotificationsTextParam;
-using ntp_snippets::kNotificationsTextValueAndMore;
-using ntp_snippets::kNotificationsTextValueSnippet;
-
-base::android::ApplicationState*
-    g_content_suggestions_notification_application_state_for_testing = nullptr;
-
-namespace {
-
-const char kNotificationIDWithinCategory[] =
-    "ContentSuggestionsNotificationIDWithinCategory";
-
-gfx::Image CropSquare(const gfx::Image& image) {
-  if (image.IsEmpty()) {
-    return image;
-  }
-  const gfx::ImageSkia* skimage = image.ToImageSkia();
-  gfx::Rect bounds{{0, 0}, skimage->size()};
-  int size = std::min(bounds.width(), bounds.height());
-  bounds.ClampToCenteredSize({size, size});
-  return gfx::Image(gfx::ImageSkiaOperations::CreateTiledImage(
-      *skimage, bounds.x(), bounds.y(), bounds.width(), bounds.height()));
-}
-
-bool ShouldNotifyInState(base::android::ApplicationState state) {
-  if (g_content_suggestions_notification_application_state_for_testing) {
-    state = *g_content_suggestions_notification_application_state_for_testing;
-  }
-  switch (state) {
-    case base::android::APPLICATION_STATE_UNKNOWN:
-    case base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES:
-      return false;
-    case base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES:
-    case base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES:
-    case base::android::APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES:
-      return true;
-  }
-  NOTREACHED();
-  return false;
-}
-
-int DayAsYYYYMMDD() {
-  base::Time::Exploded now{};
-  base::Time::Now().LocalExplode(&now);
-  return (now.year * 10000) + (now.month * 100) + now.day_of_month;
-}
-
-bool HaveQuotaForToday(PrefService* prefs) {
-  int today = DayAsYYYYMMDD();
-  int limit = variations::GetVariationParamByFeatureAsInt(
-      kNotificationsFeature, kNotificationsDailyLimit,
-      kNotificationsDefaultDailyLimit);
-  int sent =
-      prefs->GetInteger(prefs::kContentSuggestionsNotificationsSentDay) == today
-          ? prefs->GetInteger(prefs::kContentSuggestionsNotificationsSentCount)
-          : 0;
-  return sent < limit;
-}
-
-void ConsumeQuota(PrefService* prefs) {
-  int sent =
-      prefs->GetInteger(prefs::kContentSuggestionsNotificationsSentCount);
-  int today = DayAsYYYYMMDD();
-  if (prefs->GetInteger(prefs::kContentSuggestionsNotificationsSentDay) !=
-      today) {
-    prefs->SetInteger(prefs::kContentSuggestionsNotificationsSentDay, today);
-    sent = 0;  // Reset on day change.
-  }
-  prefs->SetInteger(prefs::kContentSuggestionsNotificationsSentCount, sent + 1);
-}
-
-}  // namespace
-
-class ContentSuggestionsNotifierService::NotifyingObserver
-    : public ContentSuggestionsService::Observer {
- public:
-  NotifyingObserver(ContentSuggestionsService* service,
-                    PrefService* prefs,
-                    ContentSuggestionsNotifier* notifier)
-      : service_(service),
-        prefs_(prefs),
-        notifier_(notifier),
-        app_status_listener_(base::android::ApplicationStatusListener::New(
-            base::BindRepeating(&NotifyingObserver::AppStatusChanged,
-                                base::Unretained(this)))),
-        weak_ptr_factory_(this) {}
-
-  void OnNewSuggestions(Category category) override {
-    if (!ShouldNotifyInState(app_status_listener_->GetState())) {
-      DVLOG(1) << "Suppressed notification because Chrome is frontmost";
-      return;
-    } else if (!ContentSuggestionsNotifier::ShouldSendNotifications(prefs_)) {
-      DVLOG(1) << "Suppressed notification due to opt-out";
-      return;
-    } else if (!HaveQuotaForToday(prefs_)) {
-      DVLOG(1) << "Notification suppressed due to daily limit";
-      return;
-    }
-    const ContentSuggestion* suggestion = GetSuggestionToNotifyAbout(category);
-    if (!suggestion) {
-      return;
-    }
-    base::Time timeout_at = suggestion->notification_extra()
-                                ? suggestion->notification_extra()->deadline
-                                : base::Time::Max();
-
-    const std::string text_param = variations::GetVariationParamValueByFeature(
-        kNotificationsFeature, kNotificationsTextParam);
-    base::string16 text;
-    if (text_param == kNotificationsTextValueSnippet) {
-      text = suggestion->snippet_text();
-    } else if (text_param == kNotificationsTextValueAndMore) {
-      int extra_count =
-          service_->GetSuggestionsForCategory(category).size() - 1;
-      text = l10n_util::GetStringFUTF16(
-          IDS_NTP_NOTIFICATIONS_READ_THIS_STORY_AND_MORE,
-          suggestion->publisher_name(), base::NumberToString16(extra_count));
-    } else {
-      text = suggestion->publisher_name();
-    }
-
-    bool open_to_ntp = variations::GetVariationParamByFeatureAsBool(
-        kNotificationsFeature, kNotificationsOpenToNTPParam, false);
-    service_->FetchSuggestionImage(
-        suggestion->id(),
-        base::Bind(
-            &NotifyingObserver::ImageFetched, weak_ptr_factory_.GetWeakPtr(),
-            suggestion->id(),
-            open_to_ntp ? GURL(chrome::kChromeUINewTabURL) : suggestion->url(),
-            suggestion->title(), text, timeout_at));
-  }
-
-  void OnCategoryStatusChanged(Category category,
-                               CategoryStatus new_status) override {
-    if (!category.IsKnownCategory(KnownCategories::ARTICLES)) {
-      return;
-    }
-    if (!ntp_snippets::IsCategoryStatusAvailable(new_status)) {
-      notifier_->HideAllNotifications(
-          ContentSuggestionsNotificationAction::HIDE_DISABLED);
-    }
-  }
-
-  void OnSuggestionInvalidated(
-      const ContentSuggestion::ID& suggestion_id) override {
-    notifier_->HideNotification(
-        suggestion_id, ContentSuggestionsNotificationAction::HIDE_EXPIRY);
-  }
-
-  void OnFullRefreshRequired() override {
-    notifier_->HideAllNotifications(
-        ContentSuggestionsNotificationAction::HIDE_EXPIRY);
-  }
-
-  void ContentSuggestionsServiceShutdown() override {
-    notifier_->HideAllNotifications(
-        ContentSuggestionsNotificationAction::HIDE_SHUTDOWN);
-  }
-
- private:
-  const ContentSuggestion* GetSuggestionToNotifyAbout(Category category) {
-    const auto& suggestions = service_->GetSuggestionsForCategory(category);
-    for (const ContentSuggestion& suggestion : suggestions) {
-      if (suggestion.notification_extra()) {
-        return &suggestion;
-      }
-    }
-    return nullptr;
-  }
-
-  void AppStatusChanged(base::android::ApplicationState state) {
-    if (variations::GetVariationParamByFeatureAsBool(
-            kNotificationsFeature, kNotificationsKeepWhenFrontmostParam,
-            true)) {
-      return;
-    }
-    if (!ShouldNotifyInState(state)) {
-      notifier_->HideAllNotifications(
-          ContentSuggestionsNotificationAction::HIDE_FRONTMOST);
-    }
-  }
-
-  void ImageFetched(const ContentSuggestion::ID& id,
-                    const GURL& url,
-                    const base::string16& title,
-                    const base::string16& text,
-                    base::Time timeout_at,
-                    const gfx::Image& image) {
-    if (!ShouldNotifyInState(app_status_listener_->GetState())) {
-      return;  // Became foreground while we were fetching the image; forget it.
-    }
-    // check if suggestion is still valid.
-    DVLOG(1) << "Fetched " << image.Size().width() << "x"
-             << image.Size().height() << " image for " << url.spec();
-    ConsumeQuota(prefs_);
-    int priority = variations::GetVariationParamByFeatureAsInt(
-        kNotificationsFeature, kNotificationsPriorityParam,
-        kNotificationsDefaultPriority);
-    if (notifier_->SendNotification(id, url, title, text, CropSquare(image),
-                                    timeout_at, priority)) {
-      RecordContentSuggestionsNotificationImpression(
-          id.category().IsKnownCategory(KnownCategories::ARTICLES)
-              ? ContentSuggestionsNotificationImpression::ARTICLE
-              : ContentSuggestionsNotificationImpression::NONARTICLE);
-    }
-  }
-
-  ContentSuggestionsService* const service_;
-  PrefService* const prefs_;
-  ContentSuggestionsNotifier* const notifier_;
-  std::unique_ptr<base::android::ApplicationStatusListener>
-      app_status_listener_;
-
-  base::WeakPtrFactory<NotifyingObserver> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(NotifyingObserver);
-};
-
-ContentSuggestionsNotifierService::ContentSuggestionsNotifierService(
-    PrefService* prefs,
-    ContentSuggestionsService* suggestions,
-    std::unique_ptr<ContentSuggestionsNotifier> notifier)
-    : prefs_(prefs),
-      suggestions_service_(suggestions),
-      notifier_(std::move(notifier)) {
-  notifier_->FlushCachedMetrics();
-
-  if (notifier_->RegisterChannel(
-          prefs_->GetBoolean(prefs::kContentSuggestionsNotificationsEnabled))) {
-    // Once there is a notification channel, this setting is no longer relevant
-    // and the UI to control it is gone. Set it to true so that notifications
-    // are sent unconditionally. If the channel is disabled, then notifications
-    // will be blocked at the system level, and the user can re-enable them in
-    // the system notification settings.
-    prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, true);
-  }
-
-  if (IsEnabled()) {
-    Enable();
-  } else {
-    Disable();
-  }
-}
-
-ContentSuggestionsNotifierService::~ContentSuggestionsNotifierService() =
-    default;
-
-void ContentSuggestionsNotifierService::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterBooleanPref(prefs::kContentSuggestionsNotificationsEnabled,
-                                true);
-  registry->RegisterIntegerPref(
-      prefs::kContentSuggestionsConsecutiveIgnoredPrefName, 0);
-  registry->RegisterIntegerPref(prefs::kContentSuggestionsNotificationsSentDay,
-                                0);
-  registry->RegisterIntegerPref(
-      prefs::kContentSuggestionsNotificationsSentCount, 0);
-
-  // TODO(sfiera): remove after M62; no longer (and never really) used.
-  registry->RegisterStringPref(kNotificationIDWithinCategory, std::string());
-}
-
-void ContentSuggestionsNotifierService::SetEnabled(bool enabled) {
-  prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, enabled);
-  if (enabled) {
-    Enable();
-  } else {
-    Disable();
-    RecordContentSuggestionsNotificationOptOut(
-        ContentSuggestionsNotificationOptOut::EXPLICIT);
-  }
-}
-
-bool ContentSuggestionsNotifierService::IsEnabled() const {
-  return prefs_->GetBoolean(prefs::kContentSuggestionsNotificationsEnabled);
-}
-
-void ContentSuggestionsNotifierService::Enable() {
-  if (!observer_) {
-    observer_.reset(
-        new NotifyingObserver(suggestions_service_, prefs_, notifier_.get()));
-    suggestions_service_->AddObserver(observer_.get());
-  }
-}
-
-void ContentSuggestionsNotifierService::Disable() {
-  if (observer_) {
-    suggestions_service_->RemoveObserver(observer_.get());
-    observer_.reset();
-  }
-}
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier_service.h b/chrome/browser/android/ntp/content_suggestions_notifier_service.h
deleted file mode 100644
index ce3d91f4..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier_service.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2016 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_NTP_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_H_
-#define CHROME_BROWSER_ANDROID_NTP_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "components/keyed_service/core/keyed_service.h"
-
-class ContentSuggestionsNotifier;
-class PrefService;
-
-namespace ntp_snippets {
-class ContentSuggestionsService;
-}  // namespace ntp_snippets
-
-namespace user_prefs {
-class PrefRegistrySyncable;
-}  // namespace user_prefs
-
-class ContentSuggestionsNotifierService : public KeyedService {
- public:
-  ContentSuggestionsNotifierService(
-      PrefService* prefs,
-      ntp_snippets::ContentSuggestionsService* suggestions,
-      std::unique_ptr<ContentSuggestionsNotifier> notifier);
-
-  ~ContentSuggestionsNotifierService() override;
-
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
-
-  // Set whether the service is allowed to show notifications.
-  void SetEnabled(bool enabled);
-
-  // Returns whether the service is allowed to show notifications.
-  bool IsEnabled() const;
-
- private:
-  class NotifyingObserver;
-
-  // Creates |observer_| if necessary and registers notification channel.
-  void Enable();
-
-  // Destroys |observer_| if necessary and deregisters notification channel.
-  void Disable();
-
-  PrefService* const prefs_;
-  ntp_snippets::ContentSuggestionsService* const suggestions_service_;
-  const std::unique_ptr<ContentSuggestionsNotifier> notifier_;
-
-  std::unique_ptr<NotifyingObserver> observer_;
-
-  DISALLOW_IMPLICIT_CONSTRUCTORS(ContentSuggestionsNotifierService);
-};
-
-#endif  // CHROME_BROWSER_ANDROID_NTP_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_H_
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier_service_unittest.cc b/chrome/browser/android/ntp/content_suggestions_notifier_service_unittest.cc
deleted file mode 100644
index 32a55b96..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier_service_unittest.cc
+++ /dev/null
@@ -1,291 +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 "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
-
-#include <memory>
-
-#include "base/android/application_status_listener.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/simple_test_clock.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier.h"
-#include "chrome/common/pref_names.h"
-#include "components/ntp_snippets/category_info.h"
-#include "components/ntp_snippets/category_rankers/fake_category_ranker.h"
-#include "components/ntp_snippets/content_suggestions_service.h"
-#include "components/ntp_snippets/logger.h"
-#include "components/ntp_snippets/pref_names.h"
-#include "components/ntp_snippets/remote/remote_suggestion_builder.h"
-#include "components/ntp_snippets/user_classifier.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES;
-using base::android::ApplicationState;
-using ntp_snippets::Category;
-using ntp_snippets::CategoryInfo;
-using ntp_snippets::CategoryStatus;
-using ntp_snippets::ContentSuggestion;
-using ntp_snippets::ContentSuggestionsAdditionalAction;
-using ntp_snippets::ContentSuggestionsCardLayout;
-using ntp_snippets::ContentSuggestionsProvider;
-using ntp_snippets::ContentSuggestionsService;
-using ntp_snippets::DismissedSuggestionsCallback;
-using ntp_snippets::FakeCategoryRanker;
-using ntp_snippets::FetchDoneCallback;
-using ntp_snippets::ImageFetchedCallback;
-using ntp_snippets::KnownCategories;
-using ntp_snippets::RemoteSuggestion;
-using ntp_snippets::UserClassifier;
-using ntp_snippets::test::RemoteSuggestionBuilder;
-using testing::_;
-using testing::InSequence;
-using testing::Return;
-
-extern ApplicationState*
-    g_content_suggestions_notification_application_state_for_testing;
-
-namespace {
-
-std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
-RegisteredPrefs() {
-  auto prefs = std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
-  ContentSuggestionsService::RegisterProfilePrefs(prefs->registry());
-  UserClassifier::RegisterProfilePrefs(prefs->registry());
-  ContentSuggestionsNotifierService::RegisterProfilePrefs(prefs->registry());
-  prefs->registry()->RegisterBooleanPref(
-      ntp_snippets::prefs::kArticlesListVisible, true);
-  return prefs;
-}
-
-class FakeContentSuggestionsService : public ContentSuggestionsService {
- public:
-  FakeContentSuggestionsService(PrefService* prefs, base::Clock* clock)
-      : ContentSuggestionsService(
-            ContentSuggestionsService::State::ENABLED,
-            /*identity_manager=*/nullptr,
-            /*history_service=*/nullptr,
-            /*large_icon_cache=*/nullptr,
-            prefs,
-            std::make_unique<FakeCategoryRanker>(),
-            std::make_unique<UserClassifier>(nullptr, clock),
-            /*remote_suggestions_scheduler=*/nullptr,
-            std::make_unique<ntp_snippets::Logger>()) {}
-};
-
-class FakeArticleProvider : public ContentSuggestionsProvider {
- public:
-  explicit FakeArticleProvider(ContentSuggestionsService* service)
-      : ContentSuggestionsProvider(service) {}
-
-  CategoryStatus GetCategoryStatus(Category category) override {
-    if (category.IsKnownCategory(KnownCategories::ARTICLES)) {
-      return CategoryStatus::AVAILABLE;
-    }
-    return CategoryStatus::NOT_PROVIDED;
-  }
-
-  CategoryInfo GetCategoryInfo(Category category) override {
-    DCHECK(category.IsKnownCategory(KnownCategories::ARTICLES));
-    CategoryInfo info(base::ASCIIToUTF16("title"),
-                      ContentSuggestionsCardLayout::FULL_CARD,
-                      ContentSuggestionsAdditionalAction::NONE, true,
-                      base::ASCIIToUTF16("no suggestions"));
-    return info;
-  }
-
-  void FetchSuggestionImage(const ContentSuggestion::ID& id,
-                            ImageFetchedCallback callback) override {
-    std::move(callback).Run(gfx::Image());
-  }
-
-  void FetchSuggestionImageData(
-      const ContentSuggestion::ID& suggestion_id,
-      ntp_snippets::ImageDataFetchedCallback callback) override {
-    std::move(callback).Run(std::string());
-  }
-
-  void DismissSuggestion(const ContentSuggestion::ID& id) override {
-    NOTIMPLEMENTED();
-  }
-
-  void Fetch(const Category& category,
-             const std::set<std::string>& known_suggestion_ids,
-             FetchDoneCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ClearHistory(
-      base::Time begin,
-      base::Time end,
-      const base::Callback<bool(const GURL& url)>& filter) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ClearCachedSuggestions() override { NOTIMPLEMENTED(); }
-
-  void GetDismissedSuggestionsForDebugging(
-      Category category,
-      DismissedSuggestionsCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ClearDismissedSuggestionsForDebugging(Category category) override {
-    NOTIMPLEMENTED();
-  }
-};
-
-class MockContentSuggestionsNotifier : public ContentSuggestionsNotifier {
- public:
-  MOCK_METHOD0(CreateNotificationChannel, void());
-  MOCK_METHOD7(SendNotification,
-               bool(const ContentSuggestion::ID& id,
-                    const GURL& url,
-                    const base::string16& title,
-                    const base::string16& text,
-                    const gfx::Image& image,
-                    base::Time timeout_at,
-                    int priority));
-  MOCK_METHOD2(HideNotification,
-               void(const ContentSuggestion::ID& id,
-                    ContentSuggestionsNotificationAction why));
-  MOCK_METHOD1(HideAllNotifications,
-               void(ContentSuggestionsNotificationAction why));
-  MOCK_METHOD0(FlushCachedMetrics, void());
-  MOCK_METHOD1(RegisterChannel, bool(bool enabled));
-  MOCK_METHOD0(UnregisterChannel, void());
-};
-
-class ContentSuggestionsNotifierServiceTest : public ::testing::Test {
- protected:
-  ContentSuggestionsNotifierServiceTest()
-      : application_state_(APPLICATION_STATE_HAS_PAUSED_ACTIVITIES),
-        prefs_(RegisteredPrefs()),
-        suggestions_(prefs_.get(), &clock_),
-        notifier_(new testing::StrictMock<MockContentSuggestionsNotifier>),
-        notifier_ownership_(notifier_),
-        provider_(&suggestions_) {
-    g_content_suggestions_notification_application_state_for_testing =
-        &application_state_;
-  }
-
-  ~ContentSuggestionsNotifierServiceTest() override {
-    g_content_suggestions_notification_application_state_for_testing = nullptr;
-  }
-
-  void NewSuggestions(const std::vector<std::unique_ptr<RemoteSuggestion>>&
-                          remote_suggestions) {
-    Category articles = Category::FromKnownCategory(KnownCategories::ARTICLES);
-    ContentSuggestionsProvider::Observer* observer = &suggestions_;
-    std::vector<ContentSuggestion> content_suggestions;
-    for (const auto& remote_suggestion : remote_suggestions) {
-      content_suggestions.emplace_back(
-          remote_suggestion->ToContentSuggestion(articles));
-    }
-
-    observer->OnNewSuggestions(&provider_, articles,
-                               std::move(content_suggestions));
-  }
-
-  ApplicationState application_state_;
-  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
-  base::SimpleTestClock clock_;
-  FakeContentSuggestionsService suggestions_;
-  testing::StrictMock<MockContentSuggestionsNotifier>* notifier_;
-  std::unique_ptr<ContentSuggestionsNotifier> notifier_ownership_;
-  FakeArticleProvider provider_;
-};
-
-TEST_F(ContentSuggestionsNotifierServiceTest, ShouldInitializeAtStartup) {
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-}
-
-TEST_F(ContentSuggestionsNotifierServiceTest,
-       ShouldNotInitializeAtStartupIfDisabled) {
-  prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
-
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(false)).WillOnce(Return(false));
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-}
-
-TEST_F(ContentSuggestionsNotifierServiceTest,
-       ShouldClearNotificationSettingAfterOUpgrade) {
-  prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
-
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(false))
-      .WillOnce(Return(true));  // Channel actually registered
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-
-  // Now that notification channels are used, our setting should be true
-  // unconditionally, as notification channels are used for settings instead.
-  EXPECT_EQ(true,
-            prefs_->GetBoolean(prefs::kContentSuggestionsNotificationsEnabled));
-}
-
-TEST_F(ContentSuggestionsNotifierServiceTest,
-       ShouldNotSendNotificationIfNotShouldNotify) {
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
-
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-
-  std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
-  suggestions.emplace_back(
-      RemoteSuggestionBuilder().SetShouldNotify(false).Build());
-
-  // No notification.
-  NewSuggestions(suggestions);
-}
-
-TEST_F(ContentSuggestionsNotifierServiceTest,
-       ShouldSendNotificationIfShouldNotify) {
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(true)).WillOnce(Return(false));
-
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-
-  std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
-  suggestions.emplace_back(
-      RemoteSuggestionBuilder().SetShouldNotify(true).Build());
-
-  EXPECT_CALL(*notifier_, SendNotification(_, _, _, _, _, _, _));
-  NewSuggestions(suggestions);
-}
-
-TEST_F(ContentSuggestionsNotifierServiceTest,
-       ShouldNotSendNotificationIfNotificationsDisabled) {
-  prefs_->SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
-
-  InSequence s;
-  EXPECT_CALL(*notifier_, FlushCachedMetrics());
-  EXPECT_CALL(*notifier_, RegisterChannel(false)).WillOnce(Return(false));
-
-  ContentSuggestionsNotifierService service(prefs_.get(), &suggestions_,
-                                            std::move(notifier_ownership_));
-
-  std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
-  suggestions.emplace_back(
-      RemoteSuggestionBuilder().SetShouldNotify(true).Build());
-
-  // No notification.
-  NewSuggestions(suggestions);
-}
-
-}  // namespace
diff --git a/chrome/browser/android/ntp/content_suggestions_notifier_unittest.cc b/chrome/browser/android/ntp/content_suggestions_notifier_unittest.cc
deleted file mode 100644
index 3533ac33..0000000
--- a/chrome/browser/android/ntp/content_suggestions_notifier_unittest.cc
+++ /dev/null
@@ -1,75 +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 "chrome/browser/android/ntp/content_suggestions_notifier.h"
-
-#include "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
-#include "chrome/common/pref_names.h"
-#include "components/ntp_snippets/features.h"
-#include "components/ntp_snippets/pref_names.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "components/variations/variations_params_manager.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-using ContentSuggestionsNotifierTest = ::testing::Test;
-
-TEST_F(ContentSuggestionsNotifierTest, AreNotificationsEnabled) {
-  sync_preferences::TestingPrefServiceSyncable prefs;
-  ContentSuggestionsNotifierService::RegisterProfilePrefs(prefs.registry());
-  prefs.registry()->RegisterBooleanPref(
-      ntp_snippets::prefs::kArticlesListVisible, true);
-
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-
-  prefs.SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, false);
-  EXPECT_FALSE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-
-  prefs.SetBoolean(prefs::kContentSuggestionsNotificationsEnabled, true);
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-
-  prefs.SetBoolean(ntp_snippets::prefs::kArticlesListVisible, false);
-  EXPECT_FALSE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-
-  prefs.SetBoolean(ntp_snippets::prefs::kArticlesListVisible, true);
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-}
-
-TEST_F(ContentSuggestionsNotifierTest, AreNotificationsEnabledAutoOptOut) {
-  variations::testing::VariationParamsManager params(
-      "ContentSuggestionsNotifierTest",
-      {
-          {"enable_auto_opt_out", "true"},
-          {ntp_snippets::kNotificationsIgnoredLimitParam, "3"},
-      },
-      {ntp_snippets::kNotificationsFeature.name});
-
-  sync_preferences::TestingPrefServiceSyncable prefs;
-  ContentSuggestionsNotifierService::RegisterProfilePrefs(prefs.registry());
-  prefs.registry()->RegisterBooleanPref(
-      ntp_snippets::prefs::kArticlesListVisible, true);
-
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(
-      &prefs));  // Enabled by default.
-
-  prefs.SetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName, 2);
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(
-      &prefs));  // Not yet disabled.
-
-  prefs.SetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName, 3);
-  EXPECT_FALSE(ContentSuggestionsNotifier::ShouldSendNotifications(
-      &prefs));  // Past the threshold.
-
-  // Below the threshold again; notifications are re-enabled. In practice, this
-  // could happen in one of two ways:
-  //   * There were multiple notifications visible; one was ignored, triggering
-  //     auto-opt-out, but the user opened another, resetting to 0.
-  //   * A change in variations parameters raised the limit.
-  prefs.SetInteger(prefs::kContentSuggestionsConsecutiveIgnoredPrefName, 2);
-  EXPECT_TRUE(ContentSuggestionsNotifier::ShouldSendNotifications(&prefs));
-}
-
-}  // namespace
diff --git a/chrome/browser/android/ntp/ntp_snippets_bridge.cc b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
index 158a903..6724dc3 100644
--- a/chrome/browser/android/ntp/ntp_snippets_bridge.cc
+++ b/chrome/browser/android/ntp/ntp_snippets_bridge.cc
@@ -19,14 +19,11 @@
 #include "base/feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/android/chrome_feature_list.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
 #include "chrome/browser/android/ntp/get_remote_suggestions_scheduler.h"
 #include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.h"
 #include "chrome/browser/ntp_snippets/content_suggestions_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_android.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/ntp_snippets/content_suggestion.h"
 #include "components/ntp_snippets/content_suggestions_metrics.h"
@@ -117,29 +114,6 @@
   scheduler->OnBrowserUpgraded();
 }
 
-static void JNI_SnippetsBridge_SetContentSuggestionsNotificationsEnabled(
-    JNIEnv* env,
-    jboolean enabled) {
-  ContentSuggestionsNotifierService* notifier_service =
-      ContentSuggestionsNotifierServiceFactory::GetForProfile(
-          ProfileManager::GetLastUsedProfile());
-  if (!notifier_service)
-    return;
-
-  notifier_service->SetEnabled(enabled);
-}
-
-static jboolean JNI_SnippetsBridge_AreContentSuggestionsNotificationsEnabled(
-    JNIEnv* env) {
-  ContentSuggestionsNotifierService* notifier_service =
-      ContentSuggestionsNotifierServiceFactory::GetForProfile(
-          ProfileManager::GetLastUsedProfile());
-  if (!notifier_service)
-    return false;
-
-  return notifier_service->IsEnabled();
-}
-
 NTPSnippetsBridge::NTPSnippetsBridge(JNIEnv* env,
                                      const JavaParamRef<jobject>& j_bridge,
                                      const JavaParamRef<jobject>& j_profile)
diff --git a/chrome/browser/android/preferences/prefs.h b/chrome/browser/android/preferences/prefs.h
index 27472632..55d7149 100644
--- a/chrome/browser/android/preferences/prefs.h
+++ b/chrome/browser/android/preferences/prefs.h
@@ -38,6 +38,7 @@
   AUTOFILL_CREDIT_CARD_ENABLED,
   USAGE_STATS_ENABLED,
   OFFLINE_PREFETCH_USER_SETTING_ENABLED,
+  CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED,
   // PREF_NUM_PREFS must be the last entry.
   PREF_NUM_PREFS
 };
@@ -66,6 +67,7 @@
     autofill::prefs::kAutofillCreditCardEnabled,
     prefs::kUsageStatsEnabled,
     offline_pages::prefetch_prefs::kUserSettingEnabled,
+    offline_pages::prefetch_prefs::kContentSuggestionsNotificationsEnabled,
 };
 
 #endif  // CHROME_BROWSER_ANDROID_PREFERENCES_PREFS_H_
diff --git a/chrome/browser/android/preferences/prefs_unittest.cc b/chrome/browser/android/preferences/prefs_unittest.cc
index 98d0cf57..870724be 100644
--- a/chrome/browser/android/preferences/prefs_unittest.cc
+++ b/chrome/browser/android/preferences/prefs_unittest.cc
@@ -64,6 +64,9 @@
   EXPECT_EQ(prefs::kUsageStatsEnabled, GetPrefName(USAGE_STATS_ENABLED));
   EXPECT_EQ(offline_pages::prefetch_prefs::kUserSettingEnabled,
             GetPrefName(OFFLINE_PREFETCH_USER_SETTING_ENABLED));
+  EXPECT_EQ(
+      offline_pages::prefetch_prefs::kContentSuggestionsNotificationsEnabled,
+      GetPrefName(CONTENT_SUGGESTIONS_NOTIFICATIONS_ENABLED));
 
   // If this check fails, a pref is missing a test case above.
   EXPECT_EQ(Pref::PREF_NUM_PREFS, pref_count_);
diff --git a/chrome/browser/apps/app_shim/app_shim_host_manager_browsertest_mac.mm b/chrome/browser/apps/app_shim/app_shim_host_manager_browsertest_mac.mm
index 86a59d9..91a8210 100644
--- a/chrome/browser/apps/app_shim/app_shim_host_manager_browsertest_mac.mm
+++ b/chrome/browser/apps/app_shim/app_shim_host_manager_browsertest_mac.mm
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/hash/md5.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #include "base/macros.h"
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index f07e481d..c3dacf4 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -446,6 +446,7 @@
 #include "components/crash/content/browser/child_exit_observer_android.h"
 #include "components/crash/content/browser/crash_memory_metrics_collector_android.h"
 #include "components/navigation_interception/intercept_navigation_delegate.h"
+#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
 #include "content/public/browser/android/java_interfaces.h"
 #include "services/proxy_resolver/proxy_resolver_service.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
@@ -5795,3 +5796,28 @@
 #endif
   return mime_types;
 }
+
+void ChromeContentBrowserClient::AugmentNavigationDownloadPolicy(
+    const content::WebContents* web_contents,
+    const content::RenderFrameHost* frame_host,
+    bool user_gesture,
+    content::NavigationDownloadPolicy* download_policy) const {
+  const ChromeSubresourceFilterClient* client =
+      ChromeSubresourceFilterClient::FromWebContents(web_contents);
+  if (client && client->GetThrottleManager()->IsFrameTaggedAsAd(frame_host)) {
+    if (!user_gesture) {
+      if (base::FeatureList::IsEnabled(
+              blink::features::
+                  kBlockingDownloadsInAdFrameWithoutUserActivation)) {
+        download_policy->SetDisallowed(
+            content::NavigationDownloadType::kAdFrameNoGesture);
+      } else {
+        download_policy->SetAllowed(
+            content::NavigationDownloadType::kAdFrameNoGesture);
+      }
+    } else {
+      download_policy->SetAllowed(
+          content::NavigationDownloadType::kAdFrameGesture);
+    }
+  }
+}
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index afc5f97..2417b18f 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -596,6 +596,12 @@
   base::flat_set<std::string> GetMimeHandlerViewMimeTypes(
       content::ResourceContext* resource_context) override;
 
+  void AugmentNavigationDownloadPolicy(
+      const content::WebContents* web_contents,
+      const content::RenderFrameHost* frame_host,
+      bool user_gesture,
+      content::NavigationDownloadPolicy* download_policy) const override;
+
   content::PreviewsState DetermineAllowedPreviewsWithoutHoldback(
       content::PreviewsState initial_state,
       content::NavigationHandle* navigation_handle,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index d0673c7c..c6253baf 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -627,8 +627,11 @@
     "attestation/attestation_ca_client.h",
     "attestation/attestation_policy_observer.cc",
     "attestation/attestation_policy_observer.h",
+    "attestation/certificate_uploader.h",
     "attestation/enrollment_policy_observer.cc",
     "attestation/enrollment_policy_observer.h",
+    "attestation/machine_certificate_uploader.cc",
+    "attestation/machine_certificate_uploader.h",
     "attestation/platform_verification_dialog.cc",
     "attestation/platform_verification_dialog.h",
     "attestation/platform_verification_flow.cc",
@@ -2142,6 +2145,8 @@
     "android_sms/fake_connection_establisher.h",
     "app_mode/test_kiosk_extension_builder.cc",
     "app_mode/test_kiosk_extension_builder.h",
+    "attestation/mock_certificate_uploader.cc",
+    "attestation/mock_certificate_uploader.h",
     "crostini/crostini_test_helper.cc",
     "crostini/crostini_test_helper.h",
     "drive/drivefs_test_support.cc",
@@ -2291,6 +2296,7 @@
     "attestation/enrollment_policy_observer_unittest.cc",
     "attestation/fake_certificate.cc",
     "attestation/fake_certificate.h",
+    "attestation/machine_certificate_uploader_unittest.cc",
     "attestation/platform_verification_flow_unittest.cc",
     "authpolicy/auth_policy_credentials_manager_unittest.cc",
     "authpolicy/authpolicy_helper.unittest.cc",
diff --git a/chrome/browser/chromeos/attestation/attestation_policy_observer.cc b/chrome/browser/chromeos/attestation/attestation_policy_observer.cc
index 59864b7..23f15169 100644
--- a/chrome/browser/chromeos/attestation/attestation_policy_observer.cc
+++ b/chrome/browser/chromeos/attestation/attestation_policy_observer.cc
@@ -8,138 +8,17 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/callback.h"
-#include "base/location.h"
-#include "base/optional.h"
-#include "base/task/post_task.h"
-#include "base/time/time.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
-#include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
+#include "chrome/browser/chromeos/attestation/certificate_uploader.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
-#include "chromeos/attestation/attestation_flow.h"
-#include "chromeos/cryptohome/async_method_caller.h"
-#include "chromeos/cryptohome/cryptohome_parameters.h"
-#include "chromeos/dbus/cryptohome/cryptohome_client.h"
-#include "chromeos/dbus/dbus_method_call_status.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "components/account_id/account_id.h"
-#include "components/policy/core/common/cloud/cloud_policy_client.h"
-#include "components/policy/core/common/cloud/cloud_policy_manager.h"
-#include "components/user_manager/known_user.h"
-#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_details.h"
-#include "net/cert/pem_tokenizer.h"
-#include "net/cert/x509_certificate.h"
-
-namespace {
-
-// The number of days before a certificate expires during which it is
-// considered 'expiring soon' and replacement is initiated.  The Chrome OS CA
-// issues certificates with an expiry of at least two years.  This value has
-// been set large enough so that the majority of users will have gone through
-// a full sign-in during the period.
-const int kExpiryThresholdInDays = 30;
-const int kRetryDelay = 5;  // Seconds.
-const int kRetryLimit = 100;
-
-// A dbus callback which handles a boolean result.
-//
-// Parameters
-//   on_true - Called when status=success and value=true.
-//   on_false - Called when status=success and value=false.
-//   status - The dbus operation status.
-//   result - The value returned by the dbus operation.
-void DBusBoolRedirectCallback(const base::Closure& on_true,
-                              const base::Closure& on_false,
-                              const base::Closure& on_failure,
-                              const base::Location& from_here,
-                              base::Optional<bool> result) {
-  if (!result.has_value()) {
-    LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString();
-    if (!on_failure.is_null())
-      on_failure.Run();
-    return;
-  }
-  const base::Closure& task = result.value() ? on_true : on_false;
-  if (!task.is_null())
-    task.Run();
-}
-
-// A dbus callback which handles a string result.
-//
-// Parameters
-//   on_success - Called when status=success and result=true.
-//   status - The dbus operation status.
-//   result - The result returned by the dbus operation.
-//   data - The data returned by the dbus operation.
-void DBusStringCallback(
-    const base::Callback<void(const std::string&)> on_success,
-    const base::Closure& on_failure,
-    const base::Location& from_here,
-    base::Optional<chromeos::CryptohomeClient::TpmAttestationDataResult>
-        result) {
-  if (!result.has_value() || !result->success) {
-    LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString();
-    if (!on_failure.is_null())
-      on_failure.Run();
-    return;
-  }
-  on_success.Run(result->data);
-}
-
-void DBusPrivacyCACallback(
-    const base::RepeatingCallback<void(const std::string&)> on_success,
-    const base::RepeatingCallback<
-        void(chromeos::attestation::AttestationStatus)> on_failure,
-    const base::Location& from_here,
-    chromeos::attestation::AttestationStatus status,
-    const std::string& data) {
-  if (status == chromeos::attestation::ATTESTATION_SUCCESS) {
-    on_success.Run(data);
-    return;
-  }
-  LOG(ERROR) << "Cryptohome DBus method or server called failed with status:"
-             << status << ": " << from_here.ToString();
-  if (!on_failure.is_null())
-    on_failure.Run(status);
-}
-
-}  // namespace
 
 namespace chromeos {
 namespace attestation {
 
 AttestationPolicyObserver::AttestationPolicyObserver(
-    policy::CloudPolicyClient* policy_client)
+    CertificateUploader* certificate_uploader)
     : cros_settings_(CrosSettings::Get()),
-      policy_client_(policy_client),
-      cryptohome_client_(nullptr),
-      attestation_flow_(nullptr),
-      num_retries_(0),
-      retry_limit_(kRetryLimit),
-      retry_delay_(kRetryDelay),
-      weak_factory_(this) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  attestation_subscription_ = cros_settings_->AddSettingsObserver(
-      kDeviceAttestationEnabled,
-      base::Bind(&AttestationPolicyObserver::AttestationSettingChanged,
-                 base::Unretained(this)));
-  Start();
-}
-
-AttestationPolicyObserver::AttestationPolicyObserver(
-    policy::CloudPolicyClient* policy_client,
-    CryptohomeClient* cryptohome_client,
-    AttestationFlow* attestation_flow)
-    : cros_settings_(CrosSettings::Get()),
-      policy_client_(policy_client),
-      cryptohome_client_(cryptohome_client),
-      attestation_flow_(attestation_flow),
-      num_retries_(0),
-      retry_delay_(kRetryDelay),
-      weak_factory_(this) {
+      certificate_uploader_(certificate_uploader) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   attestation_subscription_ = cros_settings_->AddSettingsObserver(
       kDeviceAttestationEnabled,
@@ -154,7 +33,6 @@
 
 void AttestationPolicyObserver::AttestationSettingChanged() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  num_retries_ = 0;
   Start();
 }
 
@@ -162,193 +40,10 @@
   // If attestation is not enabled, there is nothing to do.
   bool enabled = false;
   if (!cros_settings_->GetBoolean(kDeviceAttestationEnabled, &enabled) ||
-      !enabled)
-    return;
-
-  // We expect a registered CloudPolicyClient.
-  if (!policy_client_->is_registered()) {
-    LOG(ERROR) << "AttestationPolicyObserver: Invalid CloudPolicyClient.";
+      !enabled) {
     return;
   }
-
-  if (!cryptohome_client_)
-    cryptohome_client_ = CryptohomeClient::Get();
-
-  if (!attestation_flow_) {
-    std::unique_ptr<ServerProxy> attestation_ca_client(
-        new AttestationCAClient());
-    default_attestation_flow_.reset(new AttestationFlow(
-        cryptohome::AsyncMethodCaller::GetInstance(), cryptohome_client_,
-        std::move(attestation_ca_client)));
-    attestation_flow_ = default_attestation_flow_.get();
-  }
-
-  // Start a dbus call to check if an Enterprise Machine Key already exists.
-  base::Closure on_does_exist =
-      base::Bind(&AttestationPolicyObserver::GetExistingCertificate,
-                 weak_factory_.GetWeakPtr());
-  base::Closure on_does_not_exist =
-      base::Bind(&AttestationPolicyObserver::GetNewCertificate,
-                 weak_factory_.GetWeakPtr());
-  cryptohome_client_->TpmAttestationDoesKeyExist(
-      KEY_DEVICE,
-      cryptohome::AccountIdentifier(),  // Not used.
-      kEnterpriseMachineKey,
-      base::BindOnce(DBusBoolRedirectCallback, on_does_exist, on_does_not_exist,
-                     base::Bind(&AttestationPolicyObserver::Reschedule,
-                                weak_factory_.GetWeakPtr()),
-                     FROM_HERE));
-}
-
-void AttestationPolicyObserver::GetNewCertificate() {
-  // We can reuse the dbus callback handler logic.
-  attestation_flow_->GetCertificate(
-      PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
-      EmptyAccountId(),  // Not used.
-      std::string(),     // Not used.
-      true,              // Force a new key to be generated.
-      base::Bind(
-          [](const base::RepeatingCallback<void(const std::string&)> on_success,
-             const base::RepeatingCallback<void(AttestationStatus)> on_failure,
-             const base::Location& from_here, AttestationStatus status,
-             const std::string& data) {
-            DBusPrivacyCACallback(on_success, on_failure, from_here, status,
-                                  std::move(data));
-          },
-          base::BindRepeating(&AttestationPolicyObserver::UploadCertificate,
-                              weak_factory_.GetWeakPtr()),
-          base::BindRepeating(
-              &AttestationPolicyObserver::HandleGetCertificateFailure,
-              weak_factory_.GetWeakPtr()),
-          FROM_HERE));
-}
-
-void AttestationPolicyObserver::GetExistingCertificate() {
-  cryptohome_client_->TpmAttestationGetCertificate(
-      KEY_DEVICE,
-      cryptohome::AccountIdentifier(),  // Not used.
-      kEnterpriseMachineKey,
-      base::Bind(DBusStringCallback,
-                 base::Bind(&AttestationPolicyObserver::CheckCertificateExpiry,
-                            weak_factory_.GetWeakPtr()),
-                 base::Bind(&AttestationPolicyObserver::Reschedule,
-                            weak_factory_.GetWeakPtr()),
-                 FROM_HERE));
-}
-
-void AttestationPolicyObserver::CheckCertificateExpiry(
-    const std::string& pem_certificate_chain) {
-  int num_certificates = 0;
-  net::PEMTokenizer pem_tokenizer(pem_certificate_chain, {"CERTIFICATE"});
-  while (pem_tokenizer.GetNext()) {
-    ++num_certificates;
-    scoped_refptr<net::X509Certificate> x509 =
-        net::X509Certificate::CreateFromBytes(pem_tokenizer.data().data(),
-                                              pem_tokenizer.data().length());
-    if (!x509.get() || x509->valid_expiry().is_null()) {
-      // This logic intentionally fails open. In theory this should not happen
-      // but in practice parsing X.509 can be brittle and there are a lot of
-      // factors including which underlying module is parsing the certificate,
-      // whether that module performs more checks than just ASN.1/DER format,
-      // and the server module that generated the certificate(s). Renewal is
-      // expensive so we only renew certificates with good evidence that they
-      // have expired or will soon expire; if we don't know, we don't renew.
-      LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
-      continue;
-    }
-    const base::TimeDelta threshold =
-        base::TimeDelta::FromDays(kExpiryThresholdInDays);
-    if ((base::Time::Now() + threshold) > x509->valid_expiry()) {
-      // The certificate has expired or will soon, replace it.
-      GetNewCertificate();
-      return;
-    }
-  }
-  if (num_certificates == 0) {
-    LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry.";
-  }
-  // Get the payload and check if the certificate has already been uploaded.
-  GetKeyPayload(base::Bind(&AttestationPolicyObserver::CheckIfUploaded,
-                           weak_factory_.GetWeakPtr(),
-                           pem_certificate_chain));
-}
-
-void AttestationPolicyObserver::UploadCertificate(
-    const std::string& pem_certificate_chain) {
-  policy_client_->UploadEnterpriseMachineCertificate(
-      pem_certificate_chain,
-      base::Bind(&AttestationPolicyObserver::OnUploadComplete,
-                 weak_factory_.GetWeakPtr()));
-}
-
-void AttestationPolicyObserver::CheckIfUploaded(
-    const std::string& pem_certificate_chain,
-    const std::string& key_payload) {
-  AttestationKeyPayload payload_pb;
-  if (!key_payload.empty() &&
-      payload_pb.ParseFromString(key_payload) &&
-      payload_pb.is_certificate_uploaded()) {
-    // Already uploaded... nothing more to do.
-    return;
-  }
-  UploadCertificate(pem_certificate_chain);
-}
-
-void AttestationPolicyObserver::GetKeyPayload(
-    base::Callback<void(const std::string&)> callback) {
-  cryptohome_client_->TpmAttestationGetKeyPayload(
-      KEY_DEVICE,
-      cryptohome::AccountIdentifier(),  // Not used.
-      kEnterpriseMachineKey,
-      base::Bind(DBusStringCallback, callback,
-                 base::Bind(&AttestationPolicyObserver::Reschedule,
-                            weak_factory_.GetWeakPtr()),
-                 FROM_HERE));
-}
-
-void AttestationPolicyObserver::OnUploadComplete(bool status) {
-  if (!status)
-    return;
-  VLOG(1) << "Enterprise Machine Certificate uploaded to DMServer.";
-  GetKeyPayload(base::Bind(&AttestationPolicyObserver::MarkAsUploaded,
-                           weak_factory_.GetWeakPtr()));
-}
-
-void AttestationPolicyObserver::MarkAsUploaded(const std::string& key_payload) {
-  AttestationKeyPayload payload_pb;
-  if (!key_payload.empty())
-    payload_pb.ParseFromString(key_payload);
-  payload_pb.set_is_certificate_uploaded(true);
-  std::string new_payload;
-  if (!payload_pb.SerializeToString(&new_payload)) {
-    LOG(WARNING) << "Failed to serialize key payload.";
-    return;
-  }
-  cryptohome_client_->TpmAttestationSetKeyPayload(
-      KEY_DEVICE,
-      cryptohome::AccountIdentifier(),  // Not used.
-      kEnterpriseMachineKey, new_payload,
-      base::BindRepeating(DBusBoolRedirectCallback, base::RepeatingClosure(),
-                          base::RepeatingClosure(), base::RepeatingClosure(),
-                          FROM_HERE));
-}
-
-void AttestationPolicyObserver::HandleGetCertificateFailure(
-    AttestationStatus status) {
-  if (status != ATTESTATION_SERVER_BAD_REQUEST_FAILURE)
-    Reschedule();
-}
-
-void AttestationPolicyObserver::Reschedule() {
-  if (++num_retries_ < retry_limit_) {
-    base::PostDelayedTaskWithTraits(
-        FROM_HERE, {content::BrowserThread::UI},
-        base::BindRepeating(&AttestationPolicyObserver::Start,
-                            weak_factory_.GetWeakPtr()),
-        base::TimeDelta::FromSeconds(retry_delay_));
-  } else {
-    LOG(WARNING) << "AttestationPolicyObserver: Retry limit exceeded.";
-  }
+  certificate_uploader_->UploadCertificateIfNeeded(base::DoNothing());
 }
 
 }  // namespace attestation
diff --git a/chrome/browser/chromeos/attestation/attestation_policy_observer.h b/chrome/browser/chromeos/attestation/attestation_policy_observer.h
index 21c4f34..48c02cc4 100644
--- a/chrome/browser/chromeos/attestation/attestation_policy_observer.h
+++ b/chrome/browser/chromeos/attestation/attestation_policy_observer.h
@@ -14,43 +14,21 @@
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chromeos/dbus/constants/attestation_constants.h"
 
-namespace policy {
-class CloudPolicyClient;
-}
-
 namespace chromeos {
-
-class CrosSettings;
-class CryptohomeClient;
-
 namespace attestation {
 
-class AttestationFlow;
+class CertificateUploader;
 
-// A class which observes policy changes and triggers device attestation work if
-// necessary.
+// A class which observes policy changes and uploads a certificate if necessary.
 class AttestationPolicyObserver {
  public:
   // The observer immediately connects with CrosSettings to listen for policy
-  // changes.  The CloudPolicyClient is used to upload the device certificate to
-  // the server if one is created in response to policy changes; it must be in
-  // the registered state.  This class does not take ownership of
-  // |policy_client|.
-  explicit AttestationPolicyObserver(policy::CloudPolicyClient* policy_client);
-
-  // A constructor which allows custom CryptohomeClient and AttestationFlow
-  // implementations.  Useful for testing.
-  AttestationPolicyObserver(policy::CloudPolicyClient* policy_client,
-                            CryptohomeClient* cryptohome_client,
-                            AttestationFlow* attestation_flow);
+  // changes.  The CertificateUploader is used to obtain and upload a
+  // certificate. This class does not take ownership of |certificate_uploader|.
+  explicit AttestationPolicyObserver(CertificateUploader* certificate_uploader);
 
   ~AttestationPolicyObserver();
 
-  // Sets the retry limit in number of tries; useful in testing.
-  void set_retry_limit(int limit) { retry_limit_ = limit; }
-  // Sets the retry delay in seconds; useful in testing.
-  void set_retry_delay(int retry_delay) { retry_delay_ = retry_delay; }
-
  private:
   // Called when the attestation setting changes.
   void AttestationSettingChanged();
@@ -58,56 +36,11 @@
   // Checks attestation policy and starts any necessary work.
   void Start();
 
-  // Gets a new certificate for the Enterprise Machine Key (EMK).
-  void GetNewCertificate();
-
-  // Gets the existing EMK certificate and sends it to CheckCertificateExpiry.
-  void GetExistingCertificate();
-
-  // Checks if any certificate in the given pem_certificate_chain is expired
-  // and, if so, gets a new one. If not renewing, calls CheckIfUploaded.
-  void CheckCertificateExpiry(const std::string& pem_certificate_chain);
-
-  // Uploads a machine certificate to the policy server.
-  void UploadCertificate(const std::string& pem_certificate_chain);
-
-  // Checks if a certificate has already been uploaded and, if not, upload.
-  void CheckIfUploaded(const std::string& pem_certificate_chain,
-                       const std::string& key_payload);
-
-  // Gets the payload associated with the EMK and sends it to |callback|.
-  void GetKeyPayload(base::Callback<void(const std::string&)> callback);
-
-  // Called when a certificate upload operation completes.  On success, |status|
-  // will be true.
-  void OnUploadComplete(bool status);
-
-  // Marks a key as uploaded in the payload proto.
-  void MarkAsUploaded(const std::string& key_payload);
-
-  // Handles failure of getting a certificate.
-  void HandleGetCertificateFailure(AttestationStatus status);
-
-  // Reschedules a policy check (i.e. a call to Start) for a later time.
-  // TODO(dkrahn): A better solution would be to wait for a dbus signal which
-  // indicates the system is ready to process this task. See crbug.com/256845.
-  void Reschedule();
-
   CrosSettings* cros_settings_;
-  policy::CloudPolicyClient* policy_client_;
-  CryptohomeClient* cryptohome_client_;
-  AttestationFlow* attestation_flow_;
-  std::unique_ptr<AttestationFlow> default_attestation_flow_;
-  int num_retries_;
-  int retry_limit_;
-  int retry_delay_;
+  CertificateUploader* certificate_uploader_;
 
   std::unique_ptr<CrosSettings::ObserverSubscription> attestation_subscription_;
 
-  // Note: This should remain the last member so it'll be destroyed and
-  // invalidate the weak pointers before any other members are destroyed.
-  base::WeakPtrFactory<AttestationPolicyObserver> weak_factory_;
-
   DISALLOW_COPY_AND_ASSIGN(AttestationPolicyObserver);
 };
 
diff --git a/chrome/browser/chromeos/attestation/attestation_policy_observer_unittest.cc b/chrome/browser/chromeos/attestation/attestation_policy_observer_unittest.cc
index ec09b8f7..30c1ca8 100644
--- a/chrome/browser/chromeos/attestation/attestation_policy_observer_unittest.cc
+++ b/chrome/browser/chromeos/attestation/attestation_policy_observer_unittest.cc
@@ -12,223 +12,47 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
 #include "chrome/browser/chromeos/attestation/attestation_policy_observer.h"
-#include "chrome/browser/chromeos/attestation/fake_certificate.h"
+#include "chrome/browser/chromeos/attestation/mock_certificate_uploader.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
-#include "chromeos/attestation/mock_attestation_flow.h"
-#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
 #include "chromeos/settings/cros_settings_names.h"
-#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
-using testing::Invoke;
 using testing::StrictMock;
-using testing::WithArgs;
 
 namespace chromeos {
 namespace attestation {
 
-namespace {
-
-const int64_t kCertValid = 90;
-const int64_t kCertExpiringSoon = 20;
-const int64_t kCertExpired = -20;
-
-void CertCallbackSuccess(const AttestationFlow::CertificateCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, ATTESTATION_SUCCESS, "fake_cert"));
-}
-
-void CertCallbackUnspecifiedFailure(
-    const AttestationFlow::CertificateCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(callback, ATTESTATION_UNSPECIFIED_FAILURE, ""));
-}
-
-void CertCallbackBadRequestFailure(
-    const AttestationFlow::CertificateCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(callback, ATTESTATION_SERVER_BAD_REQUEST_FAILURE, ""));
-}
-
-void StatusCallbackSuccess(
-    const policy::CloudPolicyClient::StatusCallback& callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                base::BindOnce(callback, true));
-}
-
-}  // namespace
-
 class AttestationPolicyObserverTest : public ::testing::Test {
  public:
   AttestationPolicyObserverTest() {
     settings_helper_.ReplaceDeviceSettingsProviderWithStub();
-    settings_helper_.SetBoolean(kDeviceAttestationEnabled, true);
-    policy_client_.SetDMToken("fake_dm_token");
   }
+  ~AttestationPolicyObserverTest() override {}
 
  protected:
-  enum MockOptions {
-    MOCK_KEY_EXISTS = 1,           // Configure so a certified key exists.
-    MOCK_KEY_UPLOADED = (1 << 1),  // Configure so an upload has occurred.
-    MOCK_NEW_KEY = (1 << 2)        // Configure expecting new key generation.
-  };
-
-  // Configures mock expectations according to |mock_options|.  If options
-  // require that a certificate exists, |certificate| will be used.
-  void SetupMocks(int mock_options, const std::string& certificate) {
-    bool key_exists = (mock_options & MOCK_KEY_EXISTS);
-    // Setup expected key / cert queries.
-    if (key_exists) {
-      cryptohome_client_.SetTpmAttestationDeviceCertificate(
-          kEnterpriseMachineKey, certificate);
-    }
-
-    // Setup expected key payload queries.
-    bool key_uploaded = (mock_options & MOCK_KEY_UPLOADED);
-    cryptohome_client_.SetTpmAttestationDeviceKeyPayload(
-        kEnterpriseMachineKey, key_uploaded ? CreatePayload() : std::string());
-
-    // Setup expected key uploads.  Use WillOnce() so StrictMock will trigger an
-    // error if our expectations are not met exactly.  We want to verify that
-    // during a single run through the observer only one upload operation occurs
-    // (because it is costly) and similarly, that the writing of the uploaded
-    // status in the key payload matches the upload operation.
-    bool new_key = (mock_options & MOCK_NEW_KEY);
-    if (new_key || !key_uploaded) {
-      EXPECT_CALL(policy_client_, UploadEnterpriseMachineCertificate(
-                                      new_key ? "fake_cert" : certificate, _))
-          .WillOnce(WithArgs<1>(Invoke(StatusCallbackSuccess)));
-    }
-
-    // Setup expected key generations.  Again use WillOnce().  Key generation is
-    // another costly operation and if it gets triggered more than once during
-    // a single pass this indicates a logical problem in the observer.
-    if (new_key) {
-      EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
-          .WillOnce(WithArgs<4>(Invoke(CertCallbackSuccess)));
-    }
-  }
-
   void Run() {
-    AttestationPolicyObserver observer(&policy_client_,
-                                       &cryptohome_client_,
-                                       &attestation_flow_);
-    observer.set_retry_limit(3);
-    observer.set_retry_delay(0);
+    AttestationPolicyObserver observer(&certificate_uploader_);
     base::RunLoop().RunUntilIdle();
   }
 
-  std::string CreatePayload() {
-    AttestationKeyPayload proto;
-    proto.set_is_certificate_uploaded(true);
-    std::string serialized;
-    proto.SerializeToString(&serialized);
-    return serialized;
-  }
-
   content::TestBrowserThreadBundle test_browser_thread_bundle_;
   ScopedCrosSettingsTestHelper settings_helper_;
-  FakeCryptohomeClient cryptohome_client_;
-  StrictMock<MockAttestationFlow> attestation_flow_;
-  StrictMock<policy::MockCloudPolicyClient> policy_client_;
+  StrictMock<MockCertificateUploader> certificate_uploader_;
 };
 
+TEST_F(AttestationPolicyObserverTest, FeatureEnabled) {
+  EXPECT_CALL(certificate_uploader_, UploadCertificateIfNeeded(_));
+  settings_helper_.SetBoolean(kDeviceAttestationEnabled, true);
+  Run();
+}
+
 TEST_F(AttestationPolicyObserverTest, FeatureDisabled) {
   settings_helper_.SetBoolean(kDeviceAttestationEnabled, false);
   Run();
 }
 
-TEST_F(AttestationPolicyObserverTest, UnregisteredPolicyClient) {
-  policy_client_.SetDMToken("");
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, GetCertificateUnspecifiedFailure) {
-  EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
-      .WillRepeatedly(WithArgs<4>(Invoke(CertCallbackUnspecifiedFailure)));
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, GetCertificateBadRequestFailure) {
-  EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
-      .WillOnce(WithArgs<4>(Invoke(CertCallbackBadRequestFailure)));
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, NewCertificate) {
-  SetupMocks(MOCK_NEW_KEY, "");
-  Run();
-  EXPECT_EQ(CreatePayload(),
-            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
-                kEnterpriseMachineKey));
-}
-
-TEST_F(AttestationPolicyObserverTest, KeyExistsNotUploaded) {
-  std::string certificate;
-  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertValid),
-                                    &certificate));
-  SetupMocks(MOCK_KEY_EXISTS, certificate);
-  Run();
-  EXPECT_EQ(CreatePayload(),
-            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
-                kEnterpriseMachineKey));
-}
-
-TEST_F(AttestationPolicyObserverTest, KeyExistsAlreadyUploaded) {
-  std::string certificate;
-  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertValid),
-                                    &certificate));
-  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED, certificate);
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, KeyExistsCertExpiringSoon) {
-  std::string certificate;
-  ASSERT_TRUE(GetFakeCertificatePEM(
-      base::TimeDelta::FromDays(kCertExpiringSoon), &certificate));
-  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED | MOCK_NEW_KEY, certificate);
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, KeyExistsCertExpired) {
-  std::string certificate;
-  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertExpired),
-                                    &certificate));
-  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED | MOCK_NEW_KEY, certificate);
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, IgnoreUnknownCertFormat) {
-  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED, "unsupported");
-  Run();
-}
-
-TEST_F(AttestationPolicyObserverTest, DBusFailureRetry) {
-  SetupMocks(MOCK_NEW_KEY, "");
-  // Simulate a DBus failure.
-  cryptohome_client_.SetServiceIsAvailable(false);
-
-  // Emulate delayed service initialization.
-  // Run() instantiates an Observer, which synchronously calls
-  // TpmAttestationDoesKeyExist() and fails. During this call, we make the
-  // service available in the next run, so on retry, it will successfully
-  // return the result.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](FakeCryptohomeClient* cryptohome_client) {
-                       cryptohome_client->SetServiceIsAvailable(true);
-                     },
-                     base::Unretained(&cryptohome_client_)));
-  Run();
-  EXPECT_EQ(CreatePayload(),
-            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
-                kEnterpriseMachineKey));
-}
-
 }  // namespace attestation
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/certificate_uploader.h b/chrome/browser/chromeos/attestation/certificate_uploader.h
new file mode 100644
index 0000000..ef98be4
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/certificate_uploader.h
@@ -0,0 +1,31 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_CERTIFICATE_UPLOADER_H_
+#define CHROME_BROWSER_CHROMEOS_ATTESTATION_CERTIFICATE_UPLOADER_H_
+
+#include "base/callback.h"
+
+namespace chromeos {
+namespace attestation {
+
+// An abstract class for certificate uploaders.
+class CertificateUploader {
+ public:
+  using UploadCallback = base::OnceCallback<void(bool)>;
+
+  virtual ~CertificateUploader() = default;
+
+  // Checks if the certificate has been uploaded, and if not, do so.
+  // A certificate will be obtained if needed.
+  virtual void UploadCertificateIfNeeded(UploadCallback callback) = 0;
+
+  // Forces the refreshing of the certificate and uploads it.
+  virtual void RefreshAndUploadCertificate(UploadCallback callback) = 0;
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ATTESTATION_CERTIFICATE_UPLOADER_H_
diff --git a/chrome/browser/chromeos/attestation/machine_certificate_uploader.cc b/chrome/browser/chromeos/attestation/machine_certificate_uploader.cc
new file mode 100644
index 0000000..1ea7baa
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/machine_certificate_uploader.cc
@@ -0,0 +1,360 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/attestation/machine_certificate_uploader.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/optional.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
+#include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chromeos/attestation/attestation_flow.h"
+#include "chromeos/cryptohome/async_method_caller.h"
+#include "chromeos/cryptohome/cryptohome_parameters.h"
+#include "chromeos/dbus/cryptohome/cryptohome_client.h"
+#include "chromeos/dbus/dbus_method_call_status.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/user_manager/known_user.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "net/cert/pem_tokenizer.h"
+#include "net/cert/x509_certificate.h"
+
+namespace {
+
+// The number of days before a certificate expires during which it is
+// considered 'expiring soon' and replacement is initiated.  The Chrome OS CA
+// issues certificates with an expiry of at least two years.  This value has
+// been set large enough so that the majority of users will have gone through
+// a full sign-in during the period.
+const int kExpiryThresholdInDays = 30;
+const int kRetryDelay = 5;  // Seconds.
+const int kRetryLimit = 100;
+
+// A dbus callback which handles a boolean result.
+//
+// Parameters
+//   on_true - Called when status=success and value=true.
+//   on_false - Called when status=success and value=false.
+//   status - The dbus operation status.
+//   result - The value returned by the dbus operation.
+void DBusBoolRedirectCallback(const base::RepeatingClosure& on_true,
+                              const base::RepeatingClosure& on_false,
+                              const base::RepeatingClosure& on_failure,
+                              const base::Location& from_here,
+                              base::Optional<bool> result) {
+  if (!result.has_value()) {
+    LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString();
+    if (!on_failure.is_null())
+      on_failure.Run();
+    return;
+  }
+  const base::RepeatingClosure& task = result.value() ? on_true : on_false;
+  if (!task.is_null())
+    task.Run();
+}
+
+// A dbus callback which handles a string result.
+//
+// Parameters
+//   on_success - Called when status=success and result=true.
+//   status - The dbus operation status.
+//   result - The result returned by the dbus operation.
+//   data - The data returned by the dbus operation.
+void DBusStringCallback(
+    const base::RepeatingCallback<void(const std::string&)> on_success,
+    const base::RepeatingClosure& on_failure,
+    const base::Location& from_here,
+    base::Optional<chromeos::CryptohomeClient::TpmAttestationDataResult>
+        result) {
+  if (!result.has_value() || !result->success) {
+    LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString();
+    if (!on_failure.is_null())
+      on_failure.Run();
+    return;
+  }
+  on_success.Run(result->data);
+}
+
+void DBusPrivacyCACallback(
+    const base::RepeatingCallback<void(const std::string&)> on_success,
+    const base::RepeatingCallback<
+        void(chromeos::attestation::AttestationStatus)> on_failure,
+    const base::Location& from_here,
+    chromeos::attestation::AttestationStatus status,
+    const std::string& data) {
+  if (status == chromeos::attestation::ATTESTATION_SUCCESS) {
+    on_success.Run(data);
+    return;
+  }
+  LOG(ERROR) << "Cryptohome DBus method or server called failed with status:"
+             << status << ": " << from_here.ToString();
+  if (!on_failure.is_null())
+    on_failure.Run(status);
+}
+
+}  // namespace
+
+namespace chromeos {
+namespace attestation {
+
+MachineCertificateUploader::MachineCertificateUploader(
+    policy::CloudPolicyClient* policy_client)
+    : policy_client_(policy_client),
+      cryptohome_client_(nullptr),
+      attestation_flow_(nullptr),
+      num_retries_(0),
+      retry_limit_(kRetryLimit),
+      retry_delay_(kRetryDelay),
+      weak_factory_(this) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+MachineCertificateUploader::MachineCertificateUploader(
+    policy::CloudPolicyClient* policy_client,
+    CryptohomeClient* cryptohome_client,
+    AttestationFlow* attestation_flow)
+    : policy_client_(policy_client),
+      cryptohome_client_(cryptohome_client),
+      attestation_flow_(attestation_flow),
+      num_retries_(0),
+      retry_delay_(kRetryDelay),
+      weak_factory_(this) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+MachineCertificateUploader::~MachineCertificateUploader() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+void MachineCertificateUploader::UploadCertificateIfNeeded(
+    UploadCallback callback) {
+  refresh_certificate_ = false;
+  callback_ = std::move(callback);
+  num_retries_ = 0;
+  Start();
+}
+
+void MachineCertificateUploader::RefreshAndUploadCertificate(
+    UploadCallback callback) {
+  refresh_certificate_ = true;
+  callback_ = std::move(callback);
+  num_retries_ = 0;
+  Start();
+}
+
+void MachineCertificateUploader::Start() {
+  // We expect a registered CloudPolicyClient.
+  if (!policy_client_->is_registered()) {
+    LOG(ERROR) << "MachineCertificateUploader: Invalid CloudPolicyClient.";
+    return;
+  }
+
+  if (!cryptohome_client_)
+    cryptohome_client_ = CryptohomeClient::Get();
+
+  if (!attestation_flow_) {
+    std::unique_ptr<ServerProxy> attestation_ca_client(
+        new AttestationCAClient());
+    default_attestation_flow_.reset(new AttestationFlow(
+        cryptohome::AsyncMethodCaller::GetInstance(), cryptohome_client_,
+        std::move(attestation_ca_client)));
+    attestation_flow_ = default_attestation_flow_.get();
+  }
+
+  // Always get a new certificate if we are asked for a fresh one.
+  if (refresh_certificate_) {
+    GetNewCertificate();
+    return;
+  }
+
+  // Start a dbus call to check if an Enterprise Machine Key already exists.
+  base::RepeatingClosure on_does_not_exist =
+      base::BindRepeating(&MachineCertificateUploader::GetNewCertificate,
+                          weak_factory_.GetWeakPtr());
+  base::RepeatingClosure on_does_exist =
+      base::BindRepeating(&MachineCertificateUploader::GetExistingCertificate,
+                          weak_factory_.GetWeakPtr());
+  cryptohome_client_->TpmAttestationDoesKeyExist(
+      KEY_DEVICE,
+      cryptohome::AccountIdentifier(),  // Not used.
+      kEnterpriseMachineKey,
+      base::BindOnce(
+          DBusBoolRedirectCallback, on_does_exist, on_does_not_exist,
+          base::BindRepeating(&MachineCertificateUploader::Reschedule,
+                              weak_factory_.GetWeakPtr()),
+          FROM_HERE));
+}
+
+void MachineCertificateUploader::GetNewCertificate() {
+  // We can reuse the dbus callback handler logic.
+  attestation_flow_->GetCertificate(
+      PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
+      EmptyAccountId(),  // Not used.
+      std::string(),     // Not used.
+      true,              // Force a new key to be generated.
+      base::BindRepeating(
+          [](const base::RepeatingCallback<void(const std::string&)> on_success,
+             const base::RepeatingCallback<void(AttestationStatus)> on_failure,
+             const base::Location& from_here, AttestationStatus status,
+             const std::string& data) {
+            DBusPrivacyCACallback(on_success, on_failure, from_here, status,
+                                  std::move(data));
+          },
+          base::BindRepeating(&MachineCertificateUploader::UploadCertificate,
+                              weak_factory_.GetWeakPtr()),
+          base::BindRepeating(
+              &MachineCertificateUploader::HandleGetCertificateFailure,
+              weak_factory_.GetWeakPtr()),
+          FROM_HERE));
+}
+
+void MachineCertificateUploader::GetExistingCertificate() {
+  cryptohome_client_->TpmAttestationGetCertificate(
+      KEY_DEVICE,
+      cryptohome::AccountIdentifier(),  // Not used.
+      kEnterpriseMachineKey,
+      base::BindRepeating(
+          DBusStringCallback,
+          base::BindRepeating(
+              &MachineCertificateUploader::CheckCertificateExpiry,
+              weak_factory_.GetWeakPtr()),
+          base::BindRepeating(&MachineCertificateUploader::Reschedule,
+                              weak_factory_.GetWeakPtr()),
+          FROM_HERE));
+}
+
+void MachineCertificateUploader::CheckCertificateExpiry(
+    const std::string& pem_certificate_chain) {
+  int num_certificates = 0;
+  net::PEMTokenizer pem_tokenizer(pem_certificate_chain, {"CERTIFICATE"});
+  while (pem_tokenizer.GetNext()) {
+    ++num_certificates;
+    scoped_refptr<net::X509Certificate> x509 =
+        net::X509Certificate::CreateFromBytes(pem_tokenizer.data().data(),
+                                              pem_tokenizer.data().length());
+    if (!x509.get() || x509->valid_expiry().is_null()) {
+      // This logic intentionally fails open. In theory this should not happen
+      // but in practice parsing X.509 can be brittle and there are a lot of
+      // factors including which underlying module is parsing the certificate,
+      // whether that module performs more checks than just ASN.1/DER format,
+      // and the server module that generated the certificate(s). Renewal is
+      // expensive so we only renew certificates with good evidence that they
+      // have expired or will soon expire; if we don't know, we don't renew.
+      LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
+      continue;
+    }
+    const base::TimeDelta threshold =
+        base::TimeDelta::FromDays(kExpiryThresholdInDays);
+    if ((base::Time::Now() + threshold) > x509->valid_expiry()) {
+      // The certificate has expired or will soon, replace it.
+      GetNewCertificate();
+      return;
+    }
+  }
+  if (num_certificates == 0) {
+    LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry.";
+  }
+  // Get the payload and check if the certificate has already been uploaded.
+  GetKeyPayload(
+      base::BindRepeating(&MachineCertificateUploader::CheckIfUploaded,
+                          weak_factory_.GetWeakPtr(), pem_certificate_chain));
+}
+
+void MachineCertificateUploader::UploadCertificate(
+    const std::string& pem_certificate_chain) {
+  policy_client_->UploadEnterpriseMachineCertificate(
+      pem_certificate_chain,
+      base::BindRepeating(&MachineCertificateUploader::OnUploadComplete,
+                          weak_factory_.GetWeakPtr()));
+}
+
+void MachineCertificateUploader::CheckIfUploaded(
+    const std::string& pem_certificate_chain,
+    const std::string& key_payload) {
+  AttestationKeyPayload payload_pb;
+  if (!key_payload.empty() && payload_pb.ParseFromString(key_payload) &&
+      payload_pb.is_certificate_uploaded()) {
+    // Already uploaded... nothing more to do.
+    return;
+  }
+  UploadCertificate(pem_certificate_chain);
+}
+
+void MachineCertificateUploader::GetKeyPayload(
+    base::RepeatingCallback<void(const std::string&)> callback) {
+  cryptohome_client_->TpmAttestationGetKeyPayload(
+      KEY_DEVICE,
+      cryptohome::AccountIdentifier(),  // Not used.
+      kEnterpriseMachineKey,
+      base::BindRepeating(
+          DBusStringCallback, callback,
+          base::BindRepeating(&MachineCertificateUploader::Reschedule,
+                              weak_factory_.GetWeakPtr()),
+          FROM_HERE));
+}
+
+void MachineCertificateUploader::OnUploadComplete(bool status) {
+  if (status) {
+    VLOG(1) << "Enterprise Machine Certificate uploaded to DMServer.";
+    GetKeyPayload(
+        base::BindRepeating(&MachineCertificateUploader::MarkAsUploaded,
+                            weak_factory_.GetWeakPtr()));
+  }
+  std::move(callback_).Run(status);
+}
+
+void MachineCertificateUploader::MarkAsUploaded(
+    const std::string& key_payload) {
+  AttestationKeyPayload payload_pb;
+  if (!key_payload.empty())
+    payload_pb.ParseFromString(key_payload);
+  payload_pb.set_is_certificate_uploaded(true);
+  std::string new_payload;
+  if (!payload_pb.SerializeToString(&new_payload)) {
+    LOG(WARNING) << "Failed to serialize key payload.";
+    return;
+  }
+  cryptohome_client_->TpmAttestationSetKeyPayload(
+      KEY_DEVICE,
+      cryptohome::AccountIdentifier(),  // Not used.
+      kEnterpriseMachineKey, new_payload,
+      base::BindRepeating(DBusBoolRedirectCallback, base::RepeatingClosure(),
+                          base::RepeatingClosure(), base::RepeatingClosure(),
+                          FROM_HERE));
+}
+
+void MachineCertificateUploader::HandleGetCertificateFailure(
+    AttestationStatus status) {
+  if (status != ATTESTATION_SERVER_BAD_REQUEST_FAILURE)
+    Reschedule();
+  else
+    std::move(callback_).Run(false);
+}
+
+void MachineCertificateUploader::Reschedule() {
+  if (++num_retries_ < retry_limit_) {
+    base::PostDelayedTaskWithTraits(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindRepeating(&MachineCertificateUploader::Start,
+                            weak_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(retry_delay_));
+  } else {
+    LOG(WARNING) << "MachineCertificateUploader: Retry limit exceeded.";
+    std::move(callback_).Run(false);
+  }
+}
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/machine_certificate_uploader.h b/chrome/browser/chromeos/attestation/machine_certificate_uploader.h
new file mode 100644
index 0000000..cbb94214
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/machine_certificate_uploader.h
@@ -0,0 +1,114 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_MACHINE_CERTIFICATE_UPLOADER_H_
+#define CHROME_BROWSER_CHROMEOS_ATTESTATION_MACHINE_CERTIFICATE_UPLOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/attestation/certificate_uploader.h"
+#include "chromeos/dbus/constants/attestation_constants.h"
+
+namespace policy {
+class CloudPolicyClient;
+}
+
+namespace chromeos {
+
+class CryptohomeClient;
+
+namespace attestation {
+
+class AttestationFlow;
+
+// A class which uploads enterprise machine certificates.
+class MachineCertificateUploader : public CertificateUploader {
+ public:
+  explicit MachineCertificateUploader(policy::CloudPolicyClient* policy_client);
+
+  // A constructor which allows custom CryptohomeClient and AttestationFlow
+  // implementations.  Useful for testing.
+  MachineCertificateUploader(policy::CloudPolicyClient* policy_client,
+                             CryptohomeClient* cryptohome_client,
+                             AttestationFlow* attestation_flow);
+
+  ~MachineCertificateUploader() override;
+
+  // Sets the retry limit in number of tries; useful in testing.
+  void set_retry_limit(int limit) { retry_limit_ = limit; }
+  // Sets the retry delay in seconds; useful in testing.
+  void set_retry_delay(int retry_delay) { retry_delay_ = retry_delay; }
+
+  using UploadCallback = base::OnceCallback<void(bool)>;
+
+  // Checks if the machine certificate has been uploaded, and if not, do so.
+  // A certificate will be obtained if needed.
+  void UploadCertificateIfNeeded(UploadCallback callback) override;
+
+  // Forces the refreshing of the machine certificate and uploads it.
+  void RefreshAndUploadCertificate(UploadCallback callback) override;
+
+ private:
+  // Starts certificate obtention and upload.
+  void Start();
+
+  // Gets a new certificate for the Enterprise Machine Key (EMK).
+  void GetNewCertificate();
+
+  // Gets the existing EMK certificate and sends it to CheckCertificateExpiry.
+  void GetExistingCertificate();
+
+  // Checks if any certificate in the given pem_certificate_chain is expired
+  // and, if so, gets a new one. If not renewing, calls CheckIfUploaded.
+  void CheckCertificateExpiry(const std::string& pem_certificate_chain);
+
+  // Uploads a machine certificate to the policy server.
+  void UploadCertificate(const std::string& pem_certificate_chain);
+
+  // Checks if a certificate has already been uploaded and, if not, upload.
+  void CheckIfUploaded(const std::string& pem_certificate_chain,
+                       const std::string& key_payload);
+
+  // Gets the payload associated with the EMK and sends it to |callback|.
+  void GetKeyPayload(
+      base::RepeatingCallback<void(const std::string&)> callback);
+
+  // Called when a certificate upload operation completes.  On success, |status|
+  // will be true.
+  void OnUploadComplete(bool status);
+
+  // Marks a key as uploaded in the payload proto.
+  void MarkAsUploaded(const std::string& key_payload);
+
+  // Handles failure of getting a certificate.
+  void HandleGetCertificateFailure(AttestationStatus status);
+
+  // Reschedules a policy check (i.e. a call to Start) for a later time.
+  // TODO(dkrahn): A better solution would be to wait for a dbus signal which
+  // indicates the system is ready to process this task. See crbug.com/256845.
+  void Reschedule();
+
+  policy::CloudPolicyClient* policy_client_;
+  CryptohomeClient* cryptohome_client_;
+  AttestationFlow* attestation_flow_;
+  std::unique_ptr<AttestationFlow> default_attestation_flow_;
+  bool refresh_certificate_;
+  UploadCallback callback_;
+  int num_retries_;
+  int retry_limit_;
+  int retry_delay_;
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate the weak pointers before any other members are destroyed.
+  base::WeakPtrFactory<MachineCertificateUploader> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(MachineCertificateUploader);
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ATTESTATION_MACHINE_CERTIFICATE_UPLOADER_H_
diff --git a/chrome/browser/chromeos/attestation/machine_certificate_uploader_unittest.cc b/chrome/browser/chromeos/attestation/machine_certificate_uploader_unittest.cc
new file mode 100644
index 0000000..fabee7b
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/machine_certificate_uploader_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 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 <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
+#include "chrome/browser/chromeos/attestation/fake_certificate.h"
+#include "chrome/browser/chromeos/attestation/machine_certificate_uploader.h"
+#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
+#include "chromeos/attestation/mock_attestation_flow.h"
+#include "chromeos/dbus/cryptohome/fake_cryptohome_client.h"
+#include "chromeos/settings/cros_settings_names.h"
+#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace chromeos {
+namespace attestation {
+
+namespace {
+
+const int64_t kCertValid = 90;
+const int64_t kCertExpiringSoon = 20;
+const int64_t kCertExpired = -20;
+
+void CertCallbackSuccess(const AttestationFlow::CertificateCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, ATTESTATION_SUCCESS, "fake_cert"));
+}
+
+void CertCallbackUnspecifiedFailure(
+    const AttestationFlow::CertificateCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(callback, ATTESTATION_UNSPECIFIED_FAILURE, ""));
+}
+
+void CertCallbackBadRequestFailure(
+    const AttestationFlow::CertificateCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(callback, ATTESTATION_SERVER_BAD_REQUEST_FAILURE, ""));
+}
+
+void StatusCallbackSuccess(
+    const policy::CloudPolicyClient::StatusCallback& callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                base::BindOnce(callback, true));
+}
+
+}  // namespace
+
+class MachineCertificateUploaderTest : public ::testing::TestWithParam<bool> {
+ public:
+  MachineCertificateUploaderTest() {
+    settings_helper_.ReplaceDeviceSettingsProviderWithStub();
+    policy_client_.SetDMToken("fake_dm_token");
+  }
+
+ protected:
+  enum MockOptions {
+    MOCK_KEY_EXISTS = 1,           // Configure so a certified key exists.
+    MOCK_KEY_UPLOADED = (1 << 1),  // Configure so an upload has occurred.
+    MOCK_NEW_KEY = (1 << 2)        // Configure expecting new key generation.
+  };
+
+  // Configures mock expectations according to |mock_options|.  If options
+  // require that a certificate exists, |certificate| will be used.
+  void SetupMocks(int mock_options, const std::string& certificate) {
+    bool refresh = GetParam();
+
+    bool key_exists = (mock_options & MOCK_KEY_EXISTS);
+    // Setup expected key / cert queries.
+    if (key_exists) {
+      cryptohome_client_.SetTpmAttestationDeviceCertificate(
+          kEnterpriseMachineKey, certificate);
+    }
+
+    // Setup expected key payload queries.
+    bool key_uploaded = refresh || (mock_options & MOCK_KEY_UPLOADED);
+    cryptohome_client_.SetTpmAttestationDeviceKeyPayload(
+        kEnterpriseMachineKey, key_uploaded ? CreatePayload() : std::string());
+
+    // Setup expected key uploads.  Use WillOnce() so StrictMock will trigger an
+    // error if our expectations are not met exactly.  We want to verify that
+    // during a single run through the uploader only one upload operation occurs
+    // (because it is costly) and similarly, that the writing of the uploaded
+    // status in the key payload matches the upload operation.
+    bool new_key = refresh || (mock_options & MOCK_NEW_KEY);
+    if (new_key || !key_uploaded) {
+      EXPECT_CALL(policy_client_, UploadEnterpriseMachineCertificate(
+                                      new_key ? "fake_cert" : certificate, _))
+          .WillOnce(WithArgs<1>(Invoke(StatusCallbackSuccess)));
+    }
+
+    // Setup expected key generations.  Again use WillOnce().  Key generation is
+    // another costly operation and if it gets triggered more than once during
+    // a single pass this indicates a logical problem in the observer.
+    if (new_key) {
+      EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
+          .WillOnce(WithArgs<4>(Invoke(CertCallbackSuccess)));
+    }
+  }
+
+  void Run() {
+    MachineCertificateUploader uploader(&policy_client_, &cryptohome_client_,
+                                        &attestation_flow_);
+    uploader.set_retry_limit(3);
+    uploader.set_retry_delay(0);
+    if (GetParam())
+      uploader.RefreshAndUploadCertificate(base::DoNothing());
+    else
+      uploader.UploadCertificateIfNeeded(base::DoNothing());
+
+    base::RunLoop().RunUntilIdle();
+  }
+
+  std::string CreatePayload() {
+    AttestationKeyPayload proto;
+    proto.set_is_certificate_uploaded(true);
+    std::string serialized;
+    proto.SerializeToString(&serialized);
+    return serialized;
+  }
+
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+  ScopedCrosSettingsTestHelper settings_helper_;
+  FakeCryptohomeClient cryptohome_client_;
+  StrictMock<MockAttestationFlow> attestation_flow_;
+  StrictMock<policy::MockCloudPolicyClient> policy_client_;
+};
+
+TEST_P(MachineCertificateUploaderTest, UnregisteredPolicyClient) {
+  policy_client_.SetDMToken("");
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, GetCertificateUnspecifiedFailure) {
+  EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
+      .WillRepeatedly(WithArgs<4>(Invoke(CertCallbackUnspecifiedFailure)));
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, GetCertificateBadRequestFailure) {
+  EXPECT_CALL(attestation_flow_, GetCertificate(_, _, _, _, _))
+      .WillOnce(WithArgs<4>(Invoke(CertCallbackBadRequestFailure)));
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, NewCertificate) {
+  SetupMocks(MOCK_NEW_KEY, "");
+  Run();
+  EXPECT_EQ(CreatePayload(),
+            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
+                kEnterpriseMachineKey));
+}
+
+TEST_P(MachineCertificateUploaderTest, KeyExistsNotUploaded) {
+  std::string certificate;
+  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertValid),
+                                    &certificate));
+  SetupMocks(MOCK_KEY_EXISTS, certificate);
+  Run();
+  EXPECT_EQ(CreatePayload(),
+            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
+                kEnterpriseMachineKey));
+}
+
+TEST_P(MachineCertificateUploaderTest, KeyExistsAlreadyUploaded) {
+  std::string certificate;
+  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertValid),
+                                    &certificate));
+  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED, certificate);
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, KeyExistsCertExpiringSoon) {
+  std::string certificate;
+  ASSERT_TRUE(GetFakeCertificatePEM(
+      base::TimeDelta::FromDays(kCertExpiringSoon), &certificate));
+  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED | MOCK_NEW_KEY, certificate);
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, KeyExistsCertExpired) {
+  std::string certificate;
+  ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(kCertExpired),
+                                    &certificate));
+  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED | MOCK_NEW_KEY, certificate);
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, IgnoreUnknownCertFormat) {
+  SetupMocks(MOCK_KEY_EXISTS | MOCK_KEY_UPLOADED, "unsupported");
+  Run();
+}
+
+TEST_P(MachineCertificateUploaderTest, DBusFailureRetry) {
+  SetupMocks(MOCK_NEW_KEY, "");
+  // Simulate a DBus failure.
+  cryptohome_client_.SetServiceIsAvailable(false);
+
+  // Emulate delayed service initialization.
+  // Run() instantiates an Observer, which synchronously calls
+  // TpmAttestationDoesKeyExist() and fails. During this call, we make the
+  // service available in the next run, so on retry, it will successfully
+  // return the result.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](FakeCryptohomeClient* cryptohome_client) {
+                       cryptohome_client->SetServiceIsAvailable(true);
+                     },
+                     base::Unretained(&cryptohome_client_)));
+  Run();
+  EXPECT_EQ(CreatePayload(),
+            cryptohome_client_.GetTpmAttestationDeviceKeyPayload(
+                kEnterpriseMachineKey));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         MachineCertificateUploaderTest,
+                         testing::Values(false, true));
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/mock_certificate_uploader.cc b/chrome/browser/chromeos/attestation/mock_certificate_uploader.cc
new file mode 100644
index 0000000..9dd21d5b
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/mock_certificate_uploader.cc
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/attestation/mock_certificate_uploader.h"
+
+namespace chromeos {
+namespace attestation {
+
+MockCertificateUploader::MockCertificateUploader() = default;
+
+MockCertificateUploader::~MockCertificateUploader() = default;
+
+}  // namespace attestation
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/attestation/mock_certificate_uploader.h b/chrome/browser/chromeos/attestation/mock_certificate_uploader.h
new file mode 100644
index 0000000..1d4f6f15
--- /dev/null
+++ b/chrome/browser/chromeos/attestation/mock_certificate_uploader.h
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_CERTIFICATE_UPLOADER_H_
+#define CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_CERTIFICATE_UPLOADER_H_
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/attestation/certificate_uploader.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chromeos {
+namespace attestation {
+
+class MockCertificateUploader : public CertificateUploader {
+ public:
+  MockCertificateUploader();
+  ~MockCertificateUploader();
+
+  MOCK_METHOD1(UploadCertificateIfNeeded, void(UploadCallback));
+  MOCK_METHOD1(RefreshAndUploadCertificate, void(UploadCallback));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockCertificateUploader);
+};
+
+}  // namespace attestation
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_ATTESTATION_MOCK_CERTIFICATE_UPLOADER_H_
diff --git a/chrome/browser/chromeos/drive/drivefs_test_support.cc b/chrome/browser/chromeos/drive/drivefs_test_support.cc
index 47cfcff1..e4d160e 100644
--- a/chrome/browser/chromeos/drive/drivefs_test_support.cc
+++ b/chrome/browser/chromeos/drive/drivefs_test_support.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/files/file_util.h"
+#include "base/hash/md5.h"
 #include "base/json/json_writer.h"
 #include "base/path_service.h"
 #include "base/test/bind_test_util.h"
diff --git a/chrome/browser/chromeos/fileapi/external_file_url_util.cc b/chrome/browser/chromeos/fileapi/external_file_url_util.cc
index 822c3f70..d2b6042 100644
--- a/chrome/browser/chromeos/fileapi/external_file_url_util.cc
+++ b/chrome/browser/chromeos/fileapi/external_file_url_util.cc
@@ -46,9 +46,8 @@
 base::FilePath ExternalFileURLToVirtualPath(const GURL& url) {
   if (!url.is_valid() || url.scheme() != content::kExternalFileScheme)
     return base::FilePath();
-  std::string path_string;
-  net::UnescapeBinaryURLComponent(url.path(), &path_string);
-  return base::FilePath::FromUTF8Unsafe(path_string);
+  return base::FilePath::FromUTF8Unsafe(
+      net::UnescapeBinaryURLComponent(url.path_piece()));
 }
 
 GURL VirtualPathToExternalFileURL(const base::FilePath& virtual_path) {
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index c8afb7e..98eec5e 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/attestation/attestation_policy_observer.h"
 #include "chrome/browser/chromeos/attestation/enrollment_policy_observer.h"
+#include "chrome/browser/chromeos/attestation/machine_certificate_uploader.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h"
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
@@ -307,12 +308,15 @@
   enrollment_policy_observer_.reset(
       new chromeos::attestation::EnrollmentPolicyObserver(client()));
 
-  // Don't start the AttestationPolicyObserver if machine cert requests
-  // are disabled.
+  // Don't create a MachineCertificateUploader or start the
+  // AttestationPolicyObserver if machine cert requests are disabled.
   if (!(base::CommandLine::ForCurrentProcess()->HasSwitch(
           chromeos::switches::kDisableMachineCertRequest))) {
+    machine_certificate_uploader_.reset(
+        new chromeos::attestation::MachineCertificateUploader(client()));
     attestation_policy_observer_.reset(
-        new chromeos::attestation::AttestationPolicyObserver(client()));
+        new chromeos::attestation::AttestationPolicyObserver(
+            machine_certificate_uploader_.get()));
   }
 
   // Enable device reporting and status monitoring for cloud managed devices. We
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
index 3ba6ccb..7269ad1 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h
@@ -29,6 +29,7 @@
 namespace attestation {
 class AttestationPolicyObserver;
 class EnrollmentPolicyObserver;
+class MachineCertificateUploader;
 }
 }
 
@@ -143,6 +144,13 @@
         component_policy_disabled_for_testing;
   }
 
+  // Return a pointer to the machine certificate uploader. The callers do
+  // not take ownership of that pointer.
+  chromeos::attestation::MachineCertificateUploader*
+  GetMachineCertificateUploader() {
+    return machine_certificate_uploader_.get();
+  }
+
  private:
   // Saves the state keys received from |session_manager_client_|.
   void OnStateKeysUpdated();
@@ -186,6 +194,8 @@
 
   std::unique_ptr<chromeos::attestation::EnrollmentPolicyObserver>
       enrollment_policy_observer_;
+  std::unique_ptr<chromeos::attestation::MachineCertificateUploader>
+      machine_certificate_uploader_;
   std::unique_ptr<chromeos::attestation::AttestationPolicyObserver>
       attestation_policy_observer_;
 
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
index 2b6f348..cf50a740 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
@@ -911,8 +911,9 @@
   EXPECT_THAT(GetBody(), kSecondaryResponse);
 }
 
-IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest,
-                       BadProxiesResetWhenDisabled) {
+IN_PROC_BROWSER_TEST_F(
+    DataReductionProxyFallbackBrowsertest,
+    DISABLE_ON_WIN_MAC_CHROMEOS(BadProxiesResetWhenDisabled)) {
   base::HistogramTester histogram_tester;
   SetHeader("bypass=100");
   ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/download/download_frame_policy_browsertest.cc b/chrome/browser/download/download_frame_policy_browsertest.cc
index 64d9fe64..113957c 100644
--- a/chrome/browser/download/download_frame_policy_browsertest.cc
+++ b/chrome/browser/download/download_frame_policy_browsertest.cc
@@ -671,30 +671,18 @@
   CheckNumDownloadsExpectation();
 }
 
-// TODO(yaoxia): Combine the following two test suites when cross-process
-// navigation in ad frame is correctly handled (UseCounter is logged and
-// download gets blocked when feature is enabled). Skip testing these cases for
-// now.
 INSTANTIATE_TEST_SUITE_P(
-    TopFrameNavigatesAdSubframe,
+    /* no prefix */,
     OtherFrameNavigationDownloadBrowserTest_AdFrame,
     ::testing::Combine(
         ::testing::Bool(),
-        ::testing::Values(false),  // is_cross_origin
+        ::testing::Bool(),
         ::testing::Bool(),
         ::testing::Values(
             OtherFrameNavigationType::
+                kRestrictedSubframeNavigatesUnrestrictedTopFrame,
+            OtherFrameNavigationType::
                 kUnrestrictedTopFrameNavigatesRestrictedSubframe)));
-INSTANTIATE_TEST_SUITE_P(
-    AdSubframeNavigatesTopFrame,
-    OtherFrameNavigationDownloadBrowserTest_AdFrame,
-    ::testing::Combine(
-        ::testing::Bool(),
-        ::testing::Bool(),
-        ::testing::Bool(),
-        ::testing::Values(
-            OtherFrameNavigationType::
-                kRestrictedSubframeNavigatesUnrestrictedTopFrame)));
 
 class TopFrameSameFrameDownloadBrowserTest
     : public DownloadFramePolicyBrowserTest,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a29933b..8622a40 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -304,11 +304,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "autoplay-policy",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "background-task-component-update",
     "owners": [ "sorin", "waffles", "tiborg" ],
     "expiry_milestone": 76
@@ -872,21 +867,16 @@
     "expiry_milestone": 79
   },
   {
-    "name": "enable-autoplay-ignore-web-audio",
-    "owners": [ "beccahughes", "mlamouri", "media-dev" ],
-    "expiry_milestone": 73
-  },
-  {
-    "name": "enable-autoplay-unified-sound-settings",
-    "owners": [ "beccahughes", "mlamouri", "media-dev" ],
-    "expiry_milestone": 73
-  },
-  {
     "name": "enable-avoid-flash-between-navigation",
     "owners": [ "schenney", "paint-dev" ],
     "expiry_milestone": 76
   },
   {
+    "name": "enable-background-blur",
+    "owners": [ "newcomer" ],
+    "expiry_milestone": 86
+  },
+  {
     "name": "enable-bloated-renderer-detection",
     "owners": [ "ulan" ],
     "expiry_milestone": 75
@@ -1394,11 +1384,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-ntp-suggestions-notifications",
-    "owners": [ "fgorski" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-offer-store-unmasked-wallet-cards",
     "owners": [ "jsaul@google.com" ],
     "expiry_milestone": 76
@@ -2896,6 +2881,11 @@
     "expiry_milestone": 75
   },
   {
+    "name": "strict-origin-isolation",
+    "owners": ["wjmaclean", "alexmos", "creis"],
+    "expiry_milestone": 80
+  },
+  {
     "name": "sync-USS-autofill-profile",
     "owners": [ "jkrcal", "treib", "//components/sync/OWNERS" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index af32327..19aa113f4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -204,18 +204,6 @@
     "When enabled, the Autofill dropdown's suggestions' labels are displayed "
     "using the improved disambiguation format.";
 
-const char kAutoplayPolicyName[] = "Autoplay policy";
-const char kAutoplayPolicyDescription[] =
-    "Policy used when deciding if audio or video is allowed to autoplay.";
-
-const char kAutoplayPolicyUserGestureRequiredForCrossOrigin[] =
-    "User gesture is required for cross-origin iframes.";
-const char kAutoplayPolicyNoUserGestureRequired[] =
-    "No user gesture is required.";
-const char kAutoplayPolicyUserGestureRequired[] = "User gesture is required.";
-const char kAutoplayPolicyDocumentUserActivation[] =
-    "Document user activation is required.";
-
 const char kAwaitOptimizationName[] = "Await optimization";
 const char kAwaitOptimizationDescription[] =
     "Enables await taking 1 tick on the microtask queue.";
@@ -499,16 +487,6 @@
     "If enabled, adds the status of certain experiment variations when making "
     "calls to Google Payments.";
 
-const char kEnableAutoplayIgnoreWebAudioName[] =
-    "Autoplay ignores WebAudio playbacks";
-const char kEnableAutoplayIgnoreWebAudioDescription[] =
-    "If enabled, autoplay restrictions will be ignored for WebAudio.";
-
-const char kEnableAutoplayUnifiedSoundSettingsName[] =
-    "Autoplay unified sound settings UI";
-const char kEnableAutoplayUnifiedSoundSettingsDescription[] =
-    "If enabled, shows the new unified autoplay sound settings UI.";
-
 const char kEnableBrotliName[] = "Brotli Content-Encoding.";
 const char kEnableBrotliDescription[] =
     "Enable Brotli Content-Encoding support.";
@@ -1795,6 +1773,12 @@
     "Use committed error pages instead of transient navigation entries "
     "for SSL interstitial error pages (i.e. certificate errors).";
 
+extern const char kStrictOriginIsolationName[] = "Strict-Origin-Isolation";
+extern const char kStrictOriginIsolationDescription[] =
+    "Experimental security mode that strengthens the site isolation policy. "
+    "Controls whether site isolation should use origins instead of scheme and "
+    "eTLD+1.";
+
 const char kStopInBackgroundName[] = "Stop in background";
 const char kStopInBackgroundDescription[] =
     "Stop scheduler task queues, in the background, "
@@ -2309,12 +2293,6 @@
     "allows to override the source used to retrieve these server-side "
     "suggestions.";
 
-const char kEnableNtpSuggestionsNotificationsName[] =
-    "Notify about new content suggestions available at the New Tab page";
-const char kEnableNtpSuggestionsNotificationsDescription[] =
-    "If enabled, notifications will inform about new content suggestions on "
-    "the New Tab page.";
-
 const char kEnableOfflinePreviewsName[] = "Offline Page Previews";
 const char kEnableOfflinePreviewsDescription[] =
     "Enable showing offline page previews on slow networks.";
@@ -3072,6 +3050,10 @@
 const char kEnableAssistantVoiceMatchDescription[] =
     "Enable the Assistant Voice Match feature";
 
+const char kEnableBackgroundBlurName[] = "Enable background blur.";
+const char kEnableBackgroundBlurDescription[] =
+    "Enables background blur for the Launcher and Shelf.";
+
 const char kEnableChromeOsAccountManagerName[] = "Enable Account Manager";
 const char kEnableChromeOsAccountManagerDescription[] =
     "Enables the Chrome OS Account Manager";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 82bdd4b..8384ee8 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -141,14 +141,6 @@
 extern const char kAutofillUseImprovedLabelDisambiguationName[];
 extern const char kAutofillUseImprovedLabelDisambiguationDescription[];
 
-extern const char kAutoplayPolicyName[];
-extern const char kAutoplayPolicyDescription[];
-
-extern const char kAutoplayPolicyUserGestureRequiredForCrossOrigin[];
-extern const char kAutoplayPolicyNoUserGestureRequired[];
-extern const char kAutoplayPolicyUserGestureRequired[];
-extern const char kAutoplayPolicyDocumentUserActivation[];
-
 extern const char kAwaitOptimizationName[];
 extern const char kAwaitOptimizationDescription[];
 
@@ -322,12 +314,6 @@
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsName[];
 extern const char kEnableAutofillSendExperimentIdsInPaymentsRPCsDescription[];
 
-extern const char kEnableAutoplayIgnoreWebAudioName[];
-extern const char kEnableAutoplayIgnoreWebAudioDescription[];
-
-extern const char kEnableAutoplayUnifiedSoundSettingsName[];
-extern const char kEnableAutoplayUnifiedSoundSettingsDescription[];
-
 extern const char kEnableBrotliName[];
 extern const char kEnableBrotliDescription[];
 
@@ -1080,6 +1066,9 @@
 extern const char kStopNonTimersInBackgroundName[];
 extern const char kStopNonTimersInBackgroundDescription[];
 
+extern const char kStrictOriginIsolationName[];
+extern const char kStrictOriginIsolationDescription[];
+
 extern const char kSystemKeyboardLockName[];
 extern const char kSystemKeyboardLockDescription[];
 
@@ -1386,9 +1375,6 @@
 extern const char kEnableNtpRemoteSuggestionsName[];
 extern const char kEnableNtpRemoteSuggestionsDescription[];
 
-extern const char kEnableNtpSuggestionsNotificationsName[];
-extern const char kEnableNtpSuggestionsNotificationsDescription[];
-
 extern const char kEnableOfflinePreviewsName[];
 extern const char kEnableOfflinePreviewsDescription[];
 
@@ -1851,6 +1837,9 @@
 extern const char kEnableAssistantVoiceMatchName[];
 extern const char kEnableAssistantVoiceMatchDescription[];
 
+extern const char kEnableBackgroundBlurName[];
+extern const char kEnableBackgroundBlurDescription[];
+
 extern const char kEnableChromeOsAccountManagerName[];
 extern const char kEnableChromeOsAccountManagerDescription[];
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
index 8322c7d..6ab8b5b 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
@@ -124,11 +124,7 @@
            << ", sink ID = " << sink.sink().id() << ", app ID = " << app_id
            << ", origin = " << params.origin << ", tab ID = " << params.tab_id;
 
-  std::unique_ptr<CastActivityRecord> activity(new CastActivityRecord(
-      route, app_id, media_sink_service_, message_handler_, session_tracker_,
-      data_decoder_.get(), this));
-  auto* activity_ptr = activity.get();
-  activities_.emplace(route_id, std::move(activity));
+  auto* activity_ptr = AddActivityRecord(route, app_id);
   NotifyAllOnRoutesUpdated();
   base::TimeDelta launch_timeout = cast_source.launch_timeout();
   message_handler_->LaunchSession(
@@ -170,7 +166,7 @@
   DoLaunchSession(std::move(params));
 }
 
-bool CastActivityManager::CanJoinSession(const CastActivityRecord& activity,
+bool CastActivityManager::CanJoinSession(const CastActivityRecordBase& activity,
                                          const CastMediaSource& cast_source,
                                          bool incognito) const {
   if (!cast_source.ContainsApp(activity.app_id()))
@@ -185,7 +181,7 @@
   return true;
 }
 
-CastActivityRecord* CastActivityManager::FindActivityForSessionJoin(
+CastActivityRecordBase* CastActivityManager::FindActivityForSessionJoin(
     const CastMediaSource& cast_source,
     const std::string& presentation_id) {
   // We only allow joining by session ID. The Cast SDK uses
@@ -210,7 +206,7 @@
   return it == activities_.end() ? nullptr : it->second.get();
 }
 
-CastActivityRecord* CastActivityManager::FindActivityForAutoJoin(
+CastActivityRecordBase* CastActivityManager::FindActivityForAutoJoin(
     const CastMediaSource& cast_source,
     const url::Origin& origin,
     int tab_id) {
@@ -226,7 +222,7 @@
       activities_.begin(), activities_.end(),
       [&cast_source, &origin, tab_id](const auto& activity) {
         AutoJoinPolicy policy = cast_source.auto_join_policy();
-        const CastActivityRecord* record = activity.second.get();
+        const CastActivityRecordBase* record = activity.second.get();
         if (!record->route().is_local())
           return false;
         if (!cast_source.ContainsApp(record->app_id()))
@@ -251,7 +247,7 @@
     mojom::MediaRouteProvider::JoinRouteCallback callback) {
   DVLOG(2) << "JoinSession: source / presentation ID: "
            << cast_source.source_id() << ", " << presentation_id;
-  CastActivityRecord* activity = nullptr;
+  CastActivityRecordBase* activity = nullptr;
   if (presentation_id == kAutoJoinPresentationId) {
     activity = FindActivityForAutoJoin(cast_source, origin, tab_id);
     if (!activity && cast_source.default_action_policy() !=
@@ -393,6 +389,23 @@
       });
 }
 
+CastActivityRecordBase* CastActivityManager::AddActivityRecord(
+    const MediaRoute& route,
+    const std::string& app_id) {
+  std::unique_ptr<CastActivityRecordBase> activity;
+  if (activity_record_factory_) {
+    activity.reset(
+        activity_record_factory_->MakeCastActivityRecord(route, app_id));
+  } else {
+    activity.reset(new CastActivityRecord(route, app_id, media_sink_service_,
+                                          message_handler_, session_tracker_,
+                                          data_decoder_.get(), this));
+  }
+  auto* activity_ptr = activity.get();
+  activities_.emplace(route.media_route_id(), std::move(activity));
+  return activity_ptr;
+}
+
 void CastActivityManager::OnAppMessage(
     int channel_id,
     const cast_channel::CastMessage& message) {
@@ -404,7 +417,7 @@
     return;
   }
 
-  CastActivityRecord* activity = it->second.get();
+  CastActivityRecordBase* activity = it->second.get();
   const auto& session_id = activity->session_id();
   if (!session_id) {
     DVLOG(2) << "No session associated with activity!";
@@ -434,7 +447,7 @@
     return;
   }
 
-  CastActivityRecord* activity = activity_it->second.get();
+  CastActivityRecordBase* activity = activity_it->second.get();
   DCHECK(activity->route().media_sink_id() == sink.sink().id());
 
   DVLOG(2) << "Receiver status: update/replace activity: "
@@ -448,22 +461,23 @@
   DCHECK(existing_session_id);
 
   // If |existing_session_id| is empty, then most likely it's due to a pending
-  // launch. Check the app ID to see if the existing activity should be updated
-  // or replaced.  Otherwise, check the session ID to see if the existing
-  // activity should be updated or replaced.
+  // launch. Check the app ID to see if the existing activity should be
+  // updated or replaced.  Otherwise, check the session ID to see if the
+  // existing activity should be updated or replaced.
   if (existing_session_id ? existing_session_id == session.session_id()
                           : activity->app_id() == session.app_id()) {
     activity->SetOrUpdateSession(session, sink, hash_token_);
   } else {
-    // NOTE(jrw): This happens if a receiver switches to a new session (or app),
-    // causing the activity associated with the old session to be considered
-    // remote.  This scenario is tested in the unit tests, but it's unclear
-    // whether it even happens in practice; I haven't been able to trigger it.
+    // NOTE(jrw): This happens if a receiver switches to a new session (or
+    // app), causing the activity associated with the old session to be
+    // considered remote.  This scenario is tested in the unit tests, but it's
+    // unclear whether it even happens in practice; I haven't been able to
+    // trigger it.
     //
     // TODO(jrw): Try to come up with a test to exercise this code.
     //
-    // TODO(jrw): Figure out why this code was originally written to explicitly
-    // avoid calling NotifyAllOnRoutesUpdated().
+    // TODO(jrw): Figure out why this code was originally written to
+    // explicitly avoid calling NotifyAllOnRoutesUpdated().
     RemoveActivityWithoutNotification(
         activity_it, PresentationConnectionState::TERMINATED,
         PresentationConnectionCloseReason::CLOSED);
@@ -521,11 +535,8 @@
   MediaRoute route(route_id, source, sink_id, /* description */ std::string(),
                    /* is_local */ false, /* for_display */ true);
 
-  std::unique_ptr<CastActivityRecord> record(new CastActivityRecord(
-      route, app_id, media_sink_service_, message_handler_, session_tracker_,
-      data_decoder_.get(), this));
-  record->SetOrUpdateSession(session, sink, hash_token_);
-  activities_.emplace(route_id, std::move(record));
+  auto* activity_ptr = AddActivityRecord(route, app_id);
+  activity_ptr->SetOrUpdateSession(session, sink, hash_token_);
 }
 
 const MediaRoute* CastActivityManager::GetRoute(
@@ -659,4 +670,7 @@
 
 CastActivityManager::DoLaunchSessionParams::~DoLaunchSessionParams() = default;
 
+CastActivityRecordFactory* CastActivityManager::activity_record_factory_ =
+    nullptr;
+
 }  // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.h b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
index 8e885b2..0a2058f 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_manager.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
@@ -28,7 +28,8 @@
 
 namespace media_router {
 
-class CastActivityRecord;
+class CastActivityRecordBase;
+class CastActivityRecordFactory;
 class CastSession;
 class DataDecoder;
 class MediaSinkServiceBase;
@@ -108,13 +109,18 @@
                             const base::Value& media_status,
                             base::Optional<int> request_id) override;
 
+  static void SetActitivyRecordFactoryForTest(
+      CastActivityRecordFactory* factory) {
+    activity_record_factory_ = factory;
+  }
+
   cast_channel::ResultCallback MakeResultCallbackForRoute(
       const std::string& route_id,
       mojom::MediaRouteProvider::TerminateRouteCallback callback) override;
 
  private:
   using ActivityMap =
-      base::flat_map<MediaRoute::Id, std::unique_ptr<CastActivityRecord>>;
+      base::flat_map<MediaRoute::Id, std::unique_ptr<CastActivityRecordBase>>;
 
   // Bundle of parameters for DoLaunchSession().
   struct DoLaunchSessionParams {
@@ -188,18 +194,19 @@
       mojom::MediaRouteProvider::TerminateRouteCallback callback,
       cast_channel::Result result);
 
-  CastActivityRecord* FindActivityForAutoJoin(
+  CastActivityRecordBase* FindActivityForAutoJoin(
       const CastMediaSource& cast_source,
       const url::Origin& origin,
       int tab_id);
-  CastActivityRecord* FindActivityForSessionJoin(
-      const CastMediaSource& cast_source,
-      const std::string& presentation_id);
-  bool CanJoinSession(const CastActivityRecord& activity,
+  bool CanJoinSession(const CastActivityRecordBase& activity,
                       const CastMediaSource& cast_source,
                       bool incognito) const;
+  CastActivityRecordBase* FindActivityForSessionJoin(
+      const CastMediaSource& cast_source,
+      const std::string& presentation_id);
 
-  // Creates and stores a CastActivityRecord representing a non-local activity.
+  // Creates and stores a CastActivityRecordBase representing a non-local
+  // activity.
   void AddNonLocalActivityRecord(const MediaSinkInternal& sink,
                                  const CastSession& session);
 
@@ -210,6 +217,11 @@
   ActivityMap::iterator FindActivityByChannelId(int channel_id);
   ActivityMap::iterator FindActivityBySink(const MediaSinkInternal& sink);
 
+  CastActivityRecordBase* AddActivityRecord(const MediaRoute& route,
+                                            const std::string& app_id);
+
+  static CastActivityRecordFactory* activity_record_factory_;
+
   base::flat_set<MediaSource::Id> route_queries_;
   ActivityMap activities_;
 
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record.cc b/chrome/browser/media/router/providers/cast/cast_activity_record.cc
index ef45f61..8aa3e208 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_record.cc
+++ b/chrome/browser/media/router/providers/cast/cast_activity_record.cc
@@ -18,7 +18,13 @@
 
 namespace media_router {
 
-CastActivityRecord::~CastActivityRecord() {}
+CastActivityRecordBase::CastActivityRecordBase(const MediaRoute& route,
+                                               const std::string& app_id)
+    : route_(route), app_id_(app_id) {}
+
+CastActivityRecordBase::~CastActivityRecordBase() = default;
+
+CastActivityRecord::~CastActivityRecord() = default;
 
 mojom::RoutePresentationConnectionPtr CastActivityRecord::AddClient(
     const CastMediaSource& source,
@@ -181,8 +187,7 @@
     CastSessionTracker* session_tracker,
     DataDecoder* data_decoder,
     CastActivityManagerBase* owner)
-    : route_(route),
-      app_id_(app_id),
+    : CastActivityRecordBase(route, app_id),
       media_sink_service_(media_sink_service),
       message_handler_(message_handler),
       session_tracker_(session_tracker),
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record.h b/chrome/browser/media/router/providers/cast/cast_activity_record.h
index 0b0f7f9..afd87f9 100644
--- a/chrome/browser/media/router/providers/cast/cast_activity_record.h
+++ b/chrome/browser/media/router/providers/cast/cast_activity_record.h
@@ -24,62 +24,64 @@
 class CastInternalMessage;
 class CastSession;
 class CastSessionClientBase;
+class CastSessionClientFactory;
 class CastSessionTracker;
 class DataDecoder;
 class MediaSinkServiceBase;
 
-// Represents an ongoing or launching Cast application on a Cast receiver.
-// It keeps track of the set of Cast SDK clients connected to the application.
-// Note that we do not keep track of 1-UA mode presentations here. Instead, they
-// are handled by LocalPresentationManager.
-//
-// Instances of this class are associated with a specific session and app.
-class CastActivityRecord {
+// TODO(jrw): Rename
+//   CastActivityRecordBase -> CastActivityRecord
+//   CastActivityRecord -> CastActivityRecordImpl
+class CastActivityRecordBase {
  public:
-  ~CastActivityRecord();
+  using ClientMap =
+      base::flat_map<std::string, std::unique_ptr<CastSessionClientBase>>;
+
+  CastActivityRecordBase(const MediaRoute& route, const std::string& app_id);
+  virtual ~CastActivityRecordBase();
 
   const MediaRoute& route() const { return route_; }
   const std::string& app_id() const { return app_id_; }
-  const base::flat_map<std::string, std::unique_ptr<CastSessionClientBase>>&
-  connected_clients() const {
-    return connected_clients_;
-  }
+  const ClientMap& connected_clients() const { return connected_clients_; }
   const base::Optional<std::string>& session_id() const { return session_id_; }
 
   // Sends app message |cast_message|, which came from the SDK client, to the
   // receiver hosting this session. Returns true if the message is sent
   // successfully.
-  cast_channel::Result SendAppMessageToReceiver(
-      const CastInternalMessage& cast_message);
+  virtual cast_channel::Result SendAppMessageToReceiver(
+      const CastInternalMessage& cast_message) = 0;
 
   // Sends media command |cast_message|, which came from the SDK client, to the
   // receiver hosting this session. Returns the locally-assigned request ID of
   // the message sent to the receiver.
-  base::Optional<int> SendMediaRequestToReceiver(
-      const CastInternalMessage& cast_message);
+  virtual base::Optional<int> SendMediaRequestToReceiver(
+      const CastInternalMessage& cast_message) = 0;
 
   // Sends a SET_VOLUME request to the receiver and calls |callback| when a
   // response indicating whether the request succeeded is received.
-  void SendSetVolumeRequestToReceiver(const CastInternalMessage& cast_message,
-                                      cast_channel::ResultCallback callback);
+  virtual void SendSetVolumeRequestToReceiver(
+      const CastInternalMessage& cast_message,
+      cast_channel::ResultCallback callback) = 0;
 
-  void SendStopSessionMessageToReceiver(
+  virtual void SendStopSessionMessageToReceiver(
       const base::Optional<std::string>& client_id,
-      mojom::MediaRouteProvider::TerminateRouteCallback callback);
+      mojom::MediaRouteProvider::TerminateRouteCallback callback) = 0;
 
   // Called when the client given by |client_id| requests to leave the session.
   // This will also cause all clients within the session with matching origin
   // and/or tab ID to leave (i.e., their presentation connections will be
   // closed).
-  void HandleLeaveSession(const std::string& client_id);
+  virtual void HandleLeaveSession(const std::string& client_id) = 0;
 
-  // Adds a new client specified by |source| to this session and returns the
-  // handles of the two pipes to be held by Blink.  It is invalid to call this
-  // method if the client already exists.
-  mojom::RoutePresentationConnectionPtr AddClient(const CastMediaSource& source,
-                                                  const url::Origin& origin,
-                                                  int tab_id);
-  void RemoveClient(const std::string& client_id);
+  // Adds a new client |client_id| to this session and returns the handles of
+  // the two pipes to be held by Blink It is invalid to call this method if the
+  // client already exists.
+  virtual mojom::RoutePresentationConnectionPtr AddClient(
+      const CastMediaSource& source,
+      const url::Origin& origin,
+      int tab_id) = 0;
+
+  virtual void RemoveClient(const std::string& client_id) = 0;
 
   // On the first call, saves the ID of |session|.  On subsequent calls,
   // notifies all connected clients that the session has been updated.  In both
@@ -88,20 +90,72 @@
   //
   // The |hash_token| parameter is used for hashing receiver IDs in messages
   // sent to the Cast SDK, and |sink| is the sink associated with |session|.
-  void SetOrUpdateSession(const CastSession& session,
-                          const MediaSinkInternal& sink,
-                          const std::string& hash_token);
+  virtual void SetOrUpdateSession(const CastSession& session,
+                                  const MediaSinkInternal& sink,
+                                  const std::string& hash_token) = 0;
 
   // Sends |message| to the client given by |client_id|.
-  void SendMessageToClient(
+  virtual void SendMessageToClient(
       const std::string& client_id,
-      blink::mojom::PresentationConnectionMessagePtr message);
+      blink::mojom::PresentationConnectionMessagePtr message) = 0;
 
   // Closes / Terminates the PresentationConnections of all clients connected
   // to this activity.
+  virtual void ClosePresentationConnections(
+      blink::mojom::PresentationConnectionCloseReason close_reason) = 0;
+  virtual void TerminatePresentationConnections() = 0;
+
+ protected:
+  MediaRoute route_;
+  const std::string app_id_;
+  ClientMap connected_clients_;
+
+  // Set by CastActivityManager after the session is launched successfully.
+  base::Optional<std::string> session_id_;
+};
+
+class CastActivityRecordFactory {
+ public:
+  virtual CastActivityRecordBase* MakeCastActivityRecord(
+      const MediaRoute& route,
+      const std::string& app_id) = 0;
+};
+
+// Represents an ongoing or launching Cast application on a Cast receiver.
+// It keeps track of the set of Cast SDK clients connected to the application.
+// Note that we do not keep track of 1-UA mode presentations here. Instead, they
+// are handled by LocalPresentationManager.
+//
+// Instances of this class are associated with a specific session and app.
+class CastActivityRecord : public CastActivityRecordBase {
+ public:
+  ~CastActivityRecord() override;
+
+  // CastActivityRecordBase implementation
+  cast_channel::Result SendAppMessageToReceiver(
+      const CastInternalMessage& cast_message) override;
+  base::Optional<int> SendMediaRequestToReceiver(
+      const CastInternalMessage& cast_message) override;
+  void SendSetVolumeRequestToReceiver(
+      const CastInternalMessage& cast_message,
+      cast_channel::ResultCallback callback) override;
+  void SendStopSessionMessageToReceiver(
+      const base::Optional<std::string>& client_id,
+      mojom::MediaRouteProvider::TerminateRouteCallback callback) override;
+  void HandleLeaveSession(const std::string& client_id) override;
+  mojom::RoutePresentationConnectionPtr AddClient(const CastMediaSource& source,
+                                                  const url::Origin& origin,
+                                                  int tab_id) override;
+  void RemoveClient(const std::string& client_id) override;
+  void SetOrUpdateSession(const CastSession& session,
+                          const MediaSinkInternal& sink,
+                          const std::string& hash_token) override;
+  void SendMessageToClient(
+      const std::string& client_id,
+      blink::mojom::PresentationConnectionMessagePtr message) override;
   void ClosePresentationConnections(
-      blink::mojom::PresentationConnectionCloseReason close_reason);
-  void TerminatePresentationConnections();
+      blink::mojom::PresentationConnectionCloseReason close_reason) override;
+  void TerminatePresentationConnections() override;
 
   static void SetClientFactoryForTest(CastSessionClientFactory* factory) {
     client_factory_ = factory;
@@ -131,14 +185,6 @@
 
   static CastSessionClientFactory* client_factory_;
 
-  MediaRoute route_;
-  const std::string app_id_;
-  base::flat_map<std::string, std::unique_ptr<CastSessionClientBase>>
-      connected_clients_;
-
-  // Set by CastActivityManager after the session is launched successfully.
-  base::Optional<std::string> session_id_;
-
   MediaSinkServiceBase* const media_sink_service_;
   // TODO(https://crbug.com/809249): Consider wrapping CastMessageHandler with
   // known parameters (sink, client ID, session transport ID) and passing them
diff --git a/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.cc b/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.cc
deleted file mode 100644
index cdb69b9b..0000000
--- a/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2016 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/ntp_snippets/content_suggestions_notifier_service_factory.h"
-
-#include "base/memory/singleton.h"
-#include "chrome/browser/ntp_snippets/content_suggestions_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/ntp_snippets/features.h"
-#include "components/variations/variations_associated_data.h"
-
-#if defined(OS_ANDROID)
-#include "chrome/browser/android/ntp/android_content_suggestions_notifier.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
-#include "components/feed/feed_feature_list.h"
-#endif
-
-using ntp_snippets::kNotificationsFeature;
-
-ContentSuggestionsNotifierServiceFactory*
-ContentSuggestionsNotifierServiceFactory::GetInstance() {
-  return base::Singleton<ContentSuggestionsNotifierServiceFactory>::get();
-}
-
-ContentSuggestionsNotifierService*
-ContentSuggestionsNotifierServiceFactory::GetForProfile(Profile* profile) {
-#if defined(OS_ANDROID)
-  return static_cast<ContentSuggestionsNotifierService*>(
-      GetInstance()->GetServiceForBrowserContext(profile, true));
-#else
-  return nullptr;
-#endif
-}
-
-ContentSuggestionsNotifierService*
-ContentSuggestionsNotifierServiceFactory::GetForProfileIfExists(
-    Profile* profile) {
-#if defined(OS_ANDROID)
-  return static_cast<ContentSuggestionsNotifierService*>(
-      GetInstance()->GetServiceForBrowserContext(profile, false));
-#else
-  return nullptr;
-#endif
-}
-
-ContentSuggestionsNotifierServiceFactory::
-    ContentSuggestionsNotifierServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "ContentSuggestionsNotifierService",
-          BrowserContextDependencyManager::GetInstance()) {
-  DependsOn(ContentSuggestionsServiceFactory::GetInstance());
-}
-
-ContentSuggestionsNotifierServiceFactory::
-    ~ContentSuggestionsNotifierServiceFactory() = default;
-
-// BrowserContextKeyedServiceFactory implementation.
-KeyedService* ContentSuggestionsNotifierServiceFactory::BuildServiceInstanceFor(
-    content::BrowserContext* context) const {
-#if defined(OS_ANDROID)
-  if (base::FeatureList::IsEnabled(kNotificationsFeature) &&
-      !base::FeatureList::IsEnabled(feed::kInterestFeedContentSuggestions)) {
-    Profile* profile = Profile::FromBrowserContext(context);
-    ntp_snippets::ContentSuggestionsService* suggestions =
-        ContentSuggestionsServiceFactory::GetForProfile(profile);
-    return new ContentSuggestionsNotifierService(
-        profile->GetPrefs(), suggestions,
-        std::make_unique<AndroidContentSuggestionsNotifier>());
-  } else {
-    AndroidContentSuggestionsNotifier().UnregisterChannel();
-  }
-#endif
-  return nullptr;
-}
diff --git a/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.h b/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.h
deleted file mode 100644
index 0523895..0000000
--- a/chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2016 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_NTP_SNIPPETS_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_FACTORY_H_
-#define CHROME_BROWSER_NTP_SNIPPETS_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_FACTORY_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-class Profile;
-class ContentSuggestionsNotifierService;
-
-class ContentSuggestionsNotifierServiceFactory
-    : public BrowserContextKeyedServiceFactory {
- public:
-  static ContentSuggestionsNotifierServiceFactory* GetInstance();
-  static ContentSuggestionsNotifierService* GetForProfile(Profile* profile);
-  static ContentSuggestionsNotifierService* GetForProfileIfExists(
-      Profile* profile);
-
- private:
-  friend struct base::DefaultSingletonTraits<
-      ContentSuggestionsNotifierServiceFactory>;
-
-  ContentSuggestionsNotifierServiceFactory();
-  ~ContentSuggestionsNotifierServiceFactory() override;
-
-  // BrowserContextKeyedServiceFactory implementation.
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override;
-
-  DISALLOW_COPY_AND_ASSIGN(ContentSuggestionsNotifierServiceFactory);
-};
-
-#endif  // CHROME_BROWSER_NTP_SNIPPETS_CONTENT_SUGGESTIONS_NOTIFIER_SERVICE_FACTORY_H_
diff --git a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
index d9a5756b..50726946 100644
--- a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
@@ -7,10 +7,11 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind_helpers.h"
 #include "base/files/file_path.h"
 #include "base/memory/singleton.h"
-#include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/image_fetcher/image_fetcher_service_factory.h"
@@ -21,6 +22,7 @@
 #include "chrome/browser/offline_pages/prefetch/prefetch_instance_id_proxy.h"
 #include "chrome/browser/offline_pages/prefetch/thumbnail_fetcher_impl.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/transition_manager/full_browser_transition_manager.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/feed/feed_feature_list.h"
@@ -28,6 +30,7 @@
 #include "components/image_fetcher/core/image_fetcher_impl.h"
 #include "components/image_fetcher/core/image_fetcher_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/prefetch/prefetch_dispatcher_impl.h"
 #include "components/offline_pages/core/prefetch/prefetch_downloader_impl.h"
 #include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
@@ -37,10 +40,34 @@
 #include "components/offline_pages/core/prefetch/store/prefetch_store.h"
 #include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace offline_pages {
 
+namespace {
+
+void OnProfileCreated(PrefetchServiceImpl* service, Profile* profile) {
+  auto gcm_app_handler = std::make_unique<PrefetchGCMAppHandler>(
+      std::make_unique<PrefetchInstanceIDProxy>(kPrefetchingOfflinePagesAppId,
+                                                profile));
+  service->SetPrefetchGCMHandler(std::move(gcm_app_handler));
+  if (IsPrefetchingOfflinePagesEnabled()) {
+    // Trigger an update of the cached GCM token. This needs to be post tasked
+    // because otherwise leads to circular dependency between
+    // PrefetchServiceFactory and GCMProfileServiceFactory. See
+    // https://crbug.com/944952
+    // Update is not a priority so make sure it happens after the critical
+    // startup path.
+    content::BrowserThread::PostAfterStartupTask(
+        FROM_HERE, base::SequencedTaskRunnerHandle::Get(),
+        base::BindOnce(&PrefetchServiceImpl::GetGCMToken, service->GetWeakPtr(),
+                       base::DoNothing::Once<const std::string&>()));
+  }
+}
+
+}  // namespace
+
 PrefetchServiceFactory::PrefetchServiceFactory()
     : BrowserContextKeyedServiceFactory(
           "OfflinePagePrefetchService",
@@ -78,10 +105,6 @@
   auto prefetch_dispatcher =
       std::make_unique<PrefetchDispatcherImpl>(profile->GetPrefs());
 
-  auto prefetch_gcm_app_handler = std::make_unique<PrefetchGCMAppHandler>(
-      std::make_unique<PrefetchInstanceIDProxy>(kPrefetchingOfflinePagesAppId,
-                                                context));
-
   auto prefetch_network_request_factory =
       std::make_unique<PrefetchNetworkRequestFactoryImpl>(
           profile->GetURLLoaderFactory(), chrome::GetChannel(), GetUserAgent(),
@@ -122,14 +145,19 @@
   auto prefetch_background_task_handler =
       std::make_unique<PrefetchBackgroundTaskHandlerImpl>(profile->GetPrefs());
 
-  return new PrefetchServiceImpl(
+  auto* service = new PrefetchServiceImpl(
       std::move(offline_metrics_collector), std::move(prefetch_dispatcher),
-      std::move(prefetch_gcm_app_handler),
       std::move(prefetch_network_request_factory), offline_page_model,
       std::move(prefetch_store), std::move(suggested_articles_observer),
       std::move(prefetch_downloader), std::move(prefetch_importer),
       std::move(prefetch_background_task_handler), std::move(thumbnail_fetcher),
       image_fetcher);
+
+  auto callback = base::BindOnce(&OnProfileCreated, service);
+  FullBrowserTransitionManager::Get()->RegisterCallbackOnProfileCreation(
+      profile->GetProfileKey(), std::move(callback));
+
+  return service;
 }
 
 }  // namespace offline_pages
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 73b10de..ae78c35 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -199,7 +199,6 @@
 #include "chrome/browser/android/bookmarks/partner_bookmarks_shim.h"
 #include "chrome/browser/android/contextual_suggestions/contextual_suggestions_prefs.h"
 #include "chrome/browser/android/explore_sites/history_statistics_reporter.h"
-#include "chrome/browser/android/ntp/content_suggestions_notifier_service.h"
 #include "chrome/browser/android/ntp/recent_tabs_page_prefs.h"
 #include "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
 #include "chrome/browser/android/preferences/browser_prefs_android.h"
@@ -433,6 +432,16 @@
     "ntp_suggestions.breaking_news_gcm_last_token_validation_time";
 const char kBreakingNewsGCMLastForcedSubscriptionTime[] =
     "ntp_suggestions.breaking_news_gcm_last_forced_subscription_time";
+
+// Deprecated 4/2019.
+const char kContentSuggestionsConsecutiveIgnoredPrefName[] =
+    "ntp.content_suggestions.notifications.consecutive_ignored";
+const char kContentSuggestionsNotificationsSentDay[] =
+    "ntp.content_suggestions.notifications.sent_day";
+const char kContentSuggestionsNotificationsSentCount[] =
+    "ntp.content_suggestions.notifications.sent_count";
+const char kNotificationIDWithinCategory[] =
+    "ContentSuggestionsNotificationIDWithinCategory";
 #endif  // defined(OS_ANDROID)
 
 // Register prefs used only for migration (clearing or moving to a new key).
@@ -484,6 +493,12 @@
                                std::string());
   registry->RegisterInt64Pref(kBreakingNewsGCMLastTokenValidationTime, 0);
   registry->RegisterInt64Pref(kBreakingNewsGCMLastForcedSubscriptionTime, 0);
+
+  registry->RegisterIntegerPref(kContentSuggestionsConsecutiveIgnoredPrefName,
+                                0);
+  registry->RegisterIntegerPref(kContentSuggestionsNotificationsSentDay, 0);
+  registry->RegisterIntegerPref(kContentSuggestionsNotificationsSentCount, 0);
+  registry->RegisterStringPref(kNotificationIDWithinCategory, std::string());
 #endif  // defined(OS_ANDROID)
 }
 
@@ -776,7 +791,6 @@
   MediaDrmOriginIdManager::RegisterProfilePrefs(registry);
   contextual_suggestions::ContextualSuggestionsPrefs::RegisterProfilePrefs(
       registry);
-  ContentSuggestionsNotifierService::RegisterProfilePrefs(registry);
   explore_sites::HistoryStatisticsReporter::RegisterPrefs(registry);
   ntp_snippets::ClickBasedCategoryRanker::RegisterProfilePrefs(registry);
   OomInterventionDecider::RegisterProfilePrefs(registry);
@@ -1062,4 +1076,12 @@
   // Added 4/2019.
   syncer::ClearObsoleteSyncSpareBootstrapToken(profile_prefs);
 #endif  // defined(OS_CHROMEOS)
+
+#if defined(OS_ANDROID)
+  // Added 4/2019.
+  profile_prefs->ClearPref(kContentSuggestionsConsecutiveIgnoredPrefName);
+  profile_prefs->ClearPref(kContentSuggestionsNotificationsSentDay);
+  profile_prefs->ClearPref(kContentSuggestionsNotificationsSentCount);
+  profile_prefs->ClearPref(kNotificationIDWithinCategory);
+#endif  // defined(OS_ANDROID)
 }
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index 376d23b..98ea4a4 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -117,7 +117,6 @@
 
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/metrics/android_profile_session_durations_service_factory.h"
-#include "chrome/browser/ntp_snippets/content_suggestions_notifier_service_factory.h"
 #include "chrome/browser/ntp_snippets/content_suggestions_service_factory.h"
 #else
 #include "chrome/browser/first_run/first_run.h"
@@ -1356,8 +1355,6 @@
   // TODO(b/678590): create services during profile startup.
   // Service is responsible for fetching content snippets for the NTP.
   ContentSuggestionsServiceFactory::GetForProfile(profile);
-  // Generates notifications from the above, if experiment is enabled.
-  ContentSuggestionsNotifierServiceFactory::GetForProfile(profile);
 #endif
 
 #if defined(OS_WIN) && BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
index f548fd6c..203c215 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_graphics_tracing_ui.js
@@ -696,28 +696,34 @@
   }
 
   /**
-   * Finds the global event that is closest to the |timestamp| and not farther
-   * than |distance|.
+   * Finds global events around |timestamp| and not farther than |distance|.
    *
    * @param {number} timestamp to search.
    * @param {number} distance to search.
+   * @returns {Array} array of events sorted by distance to |timestamp| from
+   *                  closest to farthest.
    */
-  findGlobalEvent_(timestamp, distance) {
-    var bestDistance = distance;
-    var bestEvent = null;
+  findGlobalEvents_(timestamp, distance) {
+    var events = [];
+    var leftBorder = timestamp - distance;
+    var rightBorder = timestamp + distance;
     for (var i = 0; i < this.globalEvents.length; ++i) {
-      var globalEvents = this.globalEvents[i];
-      var closestIndex = this.globalEvents[i].getClosest(timestamp);
-      if (closestIndex >= 0) {
-        var testEvent = this.globalEvents[i].events[closestIndex];
-        var testDistance = Math.abs(testEvent[1] - timestamp);
-        if (testDistance < bestDistance) {
-          bestDistance = testDistance;
-          bestEvent = testEvent;
+      var index = this.globalEvents[i].getFirstAfter(leftBorder);
+      while (index >= 0) {
+        var event = this.globalEvents[i].events[index];
+        if (event[1] > rightBorder) {
+          break;
         }
+        events.push(event);
+        index = this.globalEvents[i].getNextEvent(index, 1 /* direction */);
       }
     }
-    return bestEvent;
+    events.sort(function(a, b) {
+      var distanceA = Math.abs(timestamp - a[1]);
+      var distanceB = Math.abs(timestamp - b[1]);
+      return distanceA - distanceB;
+    });
+    return events;
   }
 
   /**
@@ -740,12 +746,24 @@
       return;
     }
 
+    var svg = document.createElementNS(svgNS, 'svg');
+    svg.setAttributeNS(
+        'http://www.w3.org/2000/xmlns/', 'xmlns:xlink',
+        'http://www.w3.org/1999/xlink');
+    this.tooltip.appendChild(svg);
+
     var eventTimestamp = this.offsetToTime(eventX - this.bandOffsetX);
 
+    // Try global events fist.
+    if (this.updateToolTipForGlobalEvents_(event, svg, eventTimestamp)) {
+      return;
+    }
+
     // Find band for this mouse event.
     for (var i = 0; i < this.bands.length; ++i) {
       if (this.bands[i].top <= eventY && this.bands[i].bottom > eventY) {
-        this.updateToolTipForBand_(event, eventTimestamp, this.bands[i].band);
+        this.updateToolTipForBand_(
+            event, svg, eventTimestamp, this.bands[i].band);
         return;
       }
     }
@@ -753,7 +771,7 @@
     // Find chart for this mouse event.
     for (var i = 0; i < this.charts.length; ++i) {
       if (this.charts[i].top <= eventY && this.charts[i].bottom > eventY) {
-        this.updateToolTipForChart_(event, eventTimestamp, this.charts[i]);
+        this.updateToolTipForChart_(event, svg, eventTimestamp, this.charts[i]);
         return;
       }
     }
@@ -785,16 +803,16 @@
    * be found.
    *
    * @param {Object} svg tooltip container.
+   * @param {number} yOffset current vertical offset.
    * @param {number} eventTimestamp timestamp of the event.
    * @returns {number} vertical position of the next element.
    */
-  addTimeInfoToTooltip_(svg, eventTimestamp) {
-    var verticalGap = 5;
+  addTimeInfoToTooltip_(svg, yOffset, eventTimestamp) {
     var horizontalGap = 10;
     var fontSize = 12;
     var lineHeight = 16;
 
-    var yOffset = verticalGap + lineHeight;
+    yOffset += lineHeight;
 
     var vsyncTimestamp = this.getVSyncTimestamp_(eventTimestamp);
 
@@ -814,13 +832,78 @@
   }
 
   /**
+   * Creates and shows tooltip for global events in case they are found around
+   * mouse event position.
+   *
+   * @param {Object} mouse event.
+   * @param {Object} svg tooltip content.
+   * @param (number} eventTimestamp timestamp of event.
+   * @returns {boolean} True in case global events where found and displayed.
+   */
+  updateToolTipForGlobalEvents_(event, svg, eventTimestamp) {
+    var verticalGap = 5;
+    var horizontalGap = 10;
+    var textOffset = 16;
+    var intentOffset = 12;
+    var lineHeight = 16;
+    var fontSize = 12;
+    var width = 220;
+
+    // Try to find closest global events in the range -3..3 pixels. Several
+    // events may stick close each other so let diplay up to 3 closest events.
+    var distanceMcs = 3 * this.resolution;
+    var globalEvents = this.findGlobalEvents_(eventTimestamp, distanceMcs);
+    if (globalEvents.length == 0) {
+      return false;
+    }
+
+    // Show the global events info.
+    var globalEventCnt = Math.min(globalEvents.length, 3);
+    var yOffset = verticalGap;
+    for (var i = 0; i < globalEventCnt; ++i) {
+      var globalEvent = globalEvents[i];
+      var globalEventType = globalEvent[0];
+      var globalEventTimestamp = globalEvent[1];
+      if (globalEventType == 400 /* kVsync */) {
+        // -1 to prevent VSYNC detects itself. In last case, previous VSYNC
+        // would be chosen.
+        globalEventTimestamp -= 1;
+      }
+
+      yOffset = this.addTimeInfoToTooltip_(svg, yOffset, globalEventTimestamp);
+
+      var attributes = eventAttributes[globalEventType];
+      SVG.addText(svg, textOffset, yOffset, fontSize, attributes.name);
+      yOffset += lineHeight;
+      // Render content if exists.
+      if (globalEvent.length > 2) {
+        SVG.addText(
+            svg, textOffset + intentOffset, yOffset, fontSize, globalEvent[2]);
+        yOffset += lineHeight;
+      }
+    }
+
+    if (this.canShowDetailedInfo()) {
+      yOffset += lineHeight;
+      SVG.addText(
+          svg, horizontalGap, yOffset, fontSize, 'Click for detailed info');
+    }
+    yOffset += verticalGap;
+
+    this.showTooltipForEvent_(event, svg, yOffset, width);
+
+    return true;
+  }
+
+  /**
    * Creates and shows tooltip for event band for the position under |event|.
    *
    * @param {Object} mouse event.
+   * @param {Object} svg tooltip content.
    * @param (number} eventTimestamp timestamp of event.
    * @param {Object} active event band.
    */
-  updateToolTipForBand_(event, eventTimestamp, eventBand) {
+  updateToolTipForBand_(event, svg, eventTimestamp, eventBand) {
     var horizontalGap = 10;
     var eventIconOffset = 24;
     var eventIconRadius = 4;
@@ -831,12 +914,6 @@
     var fontSize = 12;
     var width = 220;
 
-    var svg = document.createElementNS(svgNS, 'svg');
-    svg.setAttributeNS(
-        'http://www.w3.org/2000/xmlns/', 'xmlns:xlink',
-        'http://www.w3.org/1999/xlink');
-    this.tooltip.appendChild(svg);
-
     // Find the event under the cursor. |index| points to the current event
     // and |nextIndex| points to the next event.
     var nextIndex = eventBand.getFirstEvent();
@@ -848,32 +925,7 @@
     }
     var index = eventBand.getNextEvent(nextIndex, -1 /* direction */);
 
-    // Try to find closest global event in the range -200..200 mcs from
-    // |eventTimestamp|.
-    var globalEvent = this.findGlobalEvent_(eventTimestamp, 200 /* distance */);
-    if (globalEvent) {
-      // Show the global event info.
-      var globalEventType = globalEvent[0];
-      var globalEventTimestamp = globalEvent[1];
-      if (globalEventType == 400 /* kVsync */) {
-        // -1 to prevent VSYNC detects itself. In last case, previous VSYNC
-        // would be chosen.
-        globalEventTimestamp -= 1;
-      }
-
-      var yOffset = this.addTimeInfoToTooltip_(svg, globalEventTimestamp);
-
-      var attributes = eventAttributes[globalEventType];
-      SVG.addText(svg, horizontalGap, yOffset, fontSize, attributes.name);
-      yOffset += lineHeight;
-      // Render content if exists.
-      if (globalEvent.length > 2) {
-        SVG.addText(
-            svg, horizontalGap + intentOffset, yOffset, fontSize,
-            globalEvent[2]);
-        yOffset += lineHeight;
-      }
-    } else if (index < 0 || eventBand.isEndOfSequence(index)) {
+    if (index < 0 || eventBand.isEndOfSequence(index)) {
       // In case cursor points to idle event, show its interval.
       var yOffset = verticalGap + lineHeight;
       var startIdle = index < 0 ? 0 : eventBand.events[index][1];
@@ -896,7 +948,8 @@
       }
 
       var sequenceTimestamp = eventBand.events[index][1];
-      var yOffset = this.addTimeInfoToTooltip_(svg, sequenceTimestamp);
+      var yOffset =
+          this.addTimeInfoToTooltip_(svg, verticalGap, sequenceTimestamp);
 
       var lastTimestamp = sequenceTimestamp;
       // Scan for the entries to show.
@@ -933,23 +986,24 @@
       }
     }
     if (this.canShowDetailedInfo()) {
+      yOffset += lineHeight;
       SVG.addText(
           svg, horizontalGap, yOffset, fontSize, 'Click for detailed info');
-      yOffset += lineHeight;
     }
     yOffset += verticalGap;
 
-    this.showTooltipForEvent_(event, yOffset, width);
+    this.showTooltipForEvent_(event, svg, yOffset, width);
   }
 
   /**
    * Creates and show tooltip for event chart for the position under |event|.
    *
    * @param {Object} mouse event.
+   * @param {Object} svg tooltip content.
    * @param (number} eventTimestamp timestamp of event.
    * @param {Object} active event chart.
    */
-  updateToolTipForChart_(event, eventTimestamp, chart) {
+  updateToolTipForChart_(event, svg, eventTimestamp, chart) {
     var horizontalGap = 10;
     var iconRadius = 4;
     var iconOffset = 24;
@@ -960,13 +1014,7 @@
     var width = 150;
     var iconRadius = 4;
 
-    var svg = document.createElementNS(svgNS, 'svg');
-    svg.setAttributeNS(
-        'http://www.w3.org/2000/xmlns/', 'xmlns:xlink',
-        'http://www.w3.org/1999/xlink');
-    this.tooltip.appendChild(svg);
-
-    var yOffset = this.addTimeInfoToTooltip_(svg, eventTimestamp);
+    var yOffset = this.addTimeInfoToTooltip_(svg, verticalGap, eventTimestamp);
 
     for (var i = 0; i < chart.sourcesWithBounds.length; ++i) {
       var sourceWithBounds = chart.sourcesWithBounds[i];
@@ -994,18 +1042,20 @@
       yOffset += lineHeight;
     }
 
-    this.tooltip.style.height = yOffset + 'px';
-    this.showTooltipForEvent_(event, yOffset, width);
+    this.showTooltipForEvent_(event, svg, yOffset, width);
   }
 
   /**
    * Helper that shows tooltip after filling its content.
    *
    * @param {Object} mouse event, used to determine the position of tooltip.
+   * @param {Object} svg content of tooltip.
    * @param {number} height of the tooltip view.
    * @param {number} width of the tooltip view.
    */
-  showTooltipForEvent_(event, height, width) {
+  showTooltipForEvent_(event, svg, height, width) {
+    svg.setAttribute('height', height + 'px');
+    svg.setAttribute('width', width + 'px');
     this.tooltip.style.left = event.clientX + 'px';
     this.tooltip.style.top = event.clientY + 'px';
     this.tooltip.style.height = height + 'px';
diff --git a/chrome/browser/resources/local_ntp/.eslintrc.js b/chrome/browser/resources/local_ntp/.eslintrc.js
deleted file mode 100644
index 7a5ac71..0000000
--- a/chrome/browser/resources/local_ntp/.eslintrc.js
+++ /dev/null
@@ -1,10 +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.exports = {
-  'rules': {
-    'no-var': 'off',
-    'prefer-const': 'off',
-  },
-};
diff --git a/chrome/browser/resources/local_ntp/animations.js b/chrome/browser/resources/local_ntp/animations.js
index c4cb67a1..92f9b09 100644
--- a/chrome/browser/resources/local_ntp/animations.js
+++ b/chrome/browser/resources/local_ntp/animations.js
@@ -6,7 +6,7 @@
 /**
  * Contains common animations used in the main NTP page and its iframes.
  */
-let animations = {};
+const animations = {};
 
 
 /**
@@ -43,8 +43,8 @@
  * element must have position relative or absolute.
  */
 animations.addRippleAnimations = function() {
-  let ripple = (event) => {
-    let target = event.target;
+  const ripple = (event) => {
+    const target = event.target;
     const rect = target.getBoundingClientRect();
     const x = Math.round(event.clientX - rect.left);
     const y = Math.round(event.clientY - rect.top);
@@ -56,20 +56,20 @@
       {x: 0, y: rect.height},
       {x: rect.width, y: rect.height},
     ];
-    let distance = (x1, y1, x2, y2) => {
-      var xDelta = x1 - x2;
-      var yDelta = y1 - y2;
+    const distance = (x1, y1, x2, y2) => {
+      const xDelta = x1 - x2;
+      const yDelta = y1 - y2;
       return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
     };
-    let cornerDistances = corners.map(function(corner) {
+    const cornerDistances = corners.map(function(corner) {
       return Math.round(distance(x, y, corner.x, corner.y));
     });
     const radius = Math.min(
         animations.RIPPLE_MAX_RADIUS_PX, Math.max.apply(Math, cornerDistances));
 
-    let ripple = document.createElement('div');
-    let rippleMask = document.createElement('div');
-    let rippleContainer = document.createElement('div');
+    const ripple = document.createElement('div');
+    const rippleMask = document.createElement('div');
+    const rippleContainer = document.createElement('div');
     ripple.classList.add(animations.CLASSES.RIPPLE_EFFECT);
     rippleMask.classList.add(animations.CLASSES.RIPPLE_EFFECT_MASK);
     rippleContainer.classList.add(animations.CLASSES.RIPPLE_CONTAINER);
@@ -99,7 +99,7 @@
     }, animations.RIPPLE_DURATION_MS);
   };
 
-  let rippleElements =
+  const rippleElements =
       document.querySelectorAll('.' + animations.CLASSES.RIPPLE);
   for (let i = 0; i < rippleElements.length; i++) {
     rippleElements[i].addEventListener('mousedown', ripple);
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js
index 06bf99c..6a79869 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.js
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -5,13 +5,13 @@
 
 'use strict';
 
-let customBackgrounds = {};
+const customBackgrounds = {};
 
 /**
  * The browser embeddedSearch.newTabPage object.
  * @type {Object}
  */
-var ntpApiHandle;
+let ntpApiHandle;
 
 /**
  * The different types of events that are logged from the NTP. This enum is
@@ -21,7 +21,7 @@
  * @enum {number}
  * @const
  */
-var BACKGROUND_CUSTOMIZATION_LOG_TYPE = {
+const BACKGROUND_CUSTOMIZATION_LOG_TYPE = {
   // The 'Chrome backgrounds' menu item was clicked.
   NTP_CUSTOMIZE_CHROME_BACKGROUNDS_CLICKED: 40,
   // The 'Upload an image' menu item was clicked.
@@ -220,10 +220,10 @@
  */
 customBackgrounds.setAttribution = function(
     attributionLine1, attributionLine2, attributionActionUrl) {
-  var attributionBox = $(customBackgrounds.IDS.ATTRIBUTIONS);
-  var attr1 = document.createElement('div');
+  const attributionBox = $(customBackgrounds.IDS.ATTRIBUTIONS);
+  const attr1 = document.createElement('div');
   attr1.setAttribute('id', customBackgrounds.IDS.ATTR1);
-  var attr2 = document.createElement('div');
+  const attr2 = document.createElement('div');
   attr2.setAttribute('id', customBackgrounds.IDS.ATTR2);
 
   if (attributionLine1 !== '') {
@@ -240,10 +240,10 @@
     attributionBox.appendChild(attr2);
   }
   if (attributionActionUrl !== '') {
-    var attr = (attributionLine2 !== '' ? attr2 : attr1);
+    const attr = (attributionLine2 !== '' ? attr2 : attr1);
     attr.classList.add(customBackgrounds.CLASSES.ATTR_LINK);
 
-    var linkIcon = document.createElement('div');
+    const linkIcon = document.createElement('div');
     linkIcon.id = customBackgrounds.IDS.LINK_ICON;
     // Enlarge link-icon when there is only one line of attribution
     if (attributionLine2 === '') {
@@ -262,7 +262,7 @@
 };
 
 customBackgrounds.clearAttribution = function() {
-  var attributions = $(customBackgrounds.IDS.ATTRIBUTIONS);
+  const attributions = $(customBackgrounds.IDS.ATTRIBUTIONS);
   while (attributions.firstChild) {
     attributions.removeChild(attributions.firstChild);
   }
@@ -280,7 +280,7 @@
  */
 customBackgrounds.resetSelectionDialog = function() {
   $(customBackgrounds.IDS.TILES).scrollTop = 0;
-  var tileContainer = $(customBackgrounds.IDS.TILES);
+  const tileContainer = $(customBackgrounds.IDS.TILES);
   while (tileContainer.firstChild) {
     tileContainer.removeChild(tileContainer.firstChild);
   }
@@ -310,7 +310,7 @@
  * Create a tile for a Chrome Backgrounds collection.
  */
 customBackgrounds.createChromeBackgroundTile = function(data) {
-  let tile = document.createElement('div');
+  const tile = document.createElement('div');
   tile.style.backgroundImage = 'url(' + data.previewImageUrl + ')';
   tile.dataset.id = data.collectionId;
   tile.dataset.name = data.collectionName;
@@ -349,13 +349,13 @@
   }
 
   if (deltaX != 0) {
-    let target = parseInt(current, /*radix=*/ 10) + deltaX;
+    const target = parseInt(current, /*radix=*/ 10) + deltaX;
     return $(idPrefix + target);
   } else if (deltaY != 0) {
     let target = parseInt(current, /*radix=*/ 10);
     let nextTile = $(idPrefix + target);
-    let startingTop = nextTile.getBoundingClientRect().top;
-    let startingLeft = nextTile.getBoundingClientRect().left;
+    const startingTop = nextTile.getBoundingClientRect().top;
+    const startingLeft = nextTile.getBoundingClientRect().left;
 
     // Search until a tile in a different row and the same column is found.
     while (nextTile &&
@@ -374,8 +374,8 @@
  *              collection data from.
  */
 customBackgrounds.showCollectionSelectionDialog = function(collectionsSource) {
-  var tileContainer = $(customBackgrounds.IDS.TILES);
-  var menu = $(customBackgrounds.IDS.MENU);
+  const tileContainer = $(customBackgrounds.IDS.TILES);
+  const menu = $(customBackgrounds.IDS.MENU);
   if (collectionsSource != customBackgrounds.SOURCES.CHROME_BACKGROUNDS) {
     console.log(
         'showCollectionSelectionDialog() called with invalid source=' +
@@ -395,11 +395,11 @@
   menu.classList.remove(customBackgrounds.CLASSES.IMAGE_DIALOG);
 
   // Create dialog tiles.
-  for (var i = 0; i < coll.length; ++i) {
-    let tileBackground = document.createElement('div');
+  for (let i = 0; i < coll.length; ++i) {
+    const tileBackground = document.createElement('div');
     tileBackground.classList.add(
         customBackgrounds.CLASSES.COLLECTION_TILE_BG);
-    var tile = customBackgrounds.createChromeBackgroundTile(coll[i]);
+    const tile = customBackgrounds.createChromeBackgroundTile(coll[i]);
     tile.classList.add(customBackgrounds.CLASSES.COLLECTION_TILE);
     tile.id = 'coll_tile_' + i;
     tile.dataset.tile_num = i;
@@ -407,22 +407,22 @@
     // Accessibility support for screen readers.
     tile.setAttribute('role', 'button');
 
-    var title = document.createElement('div');
+    const title = document.createElement('div');
     title.classList.add(customBackgrounds.CLASSES.COLLECTION_TITLE);
     title.textContent = tile.dataset.name;
 
-    var tileInteraction = function(event) {
-      var tile = event.target;
+    const tileInteraction = function(event) {
+      let tile = event.target;
       if (tile.classList.contains(customBackgrounds.CLASSES.COLLECTION_TITLE)) {
         tile = tile.parentNode;
       }
 
       // Load images for selected collection.
-      var imgElement = $('ntp-images-loader');
+      const imgElement = $('ntp-images-loader');
       if (imgElement) {
         imgElement.parentNode.removeChild(imgElement);
       }
-      var imgScript = document.createElement('script');
+      const imgScript = document.createElement('script');
       imgScript.id = 'ntp-images-loader';
       imgScript.src = 'chrome-search://local-ntp/ntp-background-images.js?' +
           'collection_id=' + tile.dataset.id;
@@ -434,7 +434,7 @@
 
       imgScript.onload = function() {
         // Verify that the individual image data was successfully loaded.
-        var imageDataLoaded =
+        const imageDataLoaded =
             (collImg.length > 0 && collImg[0].collectionId == tile.dataset.id);
 
         // Dependent upon the success of the load, populate the image selection
@@ -501,9 +501,9 @@
  */
 customBackgrounds.applySelectedState = function(tile) {
   tile.classList.add(customBackgrounds.CLASSES.COLLECTION_SELECTED);
-  var selectedBorder = document.createElement('div');
-  var selectedCircle = document.createElement('div');
-  var selectedCheck = document.createElement('div');
+  const selectedBorder = document.createElement('div');
+  const selectedCircle = document.createElement('div');
+  const selectedCheck = document.createElement('div');
   selectedBorder.classList.add(customBackgrounds.CLASSES.SELECTED_BORDER);
   selectedCircle.classList.add(customBackgrounds.CLASSES.SELECTED_CIRCLE);
   selectedCheck.classList.add(customBackgrounds.CLASSES.SELECTED_CHECK);
@@ -536,21 +536,21 @@
 customBackgrounds.showImageSelectionDialog = function(dialogTitle) {
   const firstNTile = customBackgrounds.ROWS_TO_PRELOAD
       * customBackgrounds.getTilesWide();
-  var menu = $(customBackgrounds.IDS.MENU);
-  var tileContainer = $(customBackgrounds.IDS.TILES);
+  const menu = $(customBackgrounds.IDS.MENU);
+  const tileContainer = $(customBackgrounds.IDS.TILES);
 
   $(customBackgrounds.IDS.TITLE).textContent = dialogTitle;
   menu.classList.remove(customBackgrounds.CLASSES.COLLECTION_DIALOG);
   menu.classList.add(customBackgrounds.CLASSES.IMAGE_DIALOG);
 
-  let preLoadTiles = [];
-  let postLoadTiles = [];
+  const preLoadTiles = [];
+  const postLoadTiles = [];
 
-  for (var i = 0; i < collImg.length; ++i) {
-    let tileBackground = document.createElement('div');
+  for (let i = 0; i < collImg.length; ++i) {
+    const tileBackground = document.createElement('div');
     tileBackground.classList.add(
         customBackgrounds.CLASSES.COLLECTION_TILE_BG);
-    var tile = document.createElement('div');
+    const tile = document.createElement('div');
     tile.classList.add(customBackgrounds.CLASSES.COLLECTION_TILE);
     // Accessibility support for screen readers.
     tile.setAttribute('role', 'button');
@@ -586,7 +586,7 @@
       postLoadTiles.push(tile);
     }
 
-    let tileInteraction = function(tile) {
+    const tileInteraction = function(tile) {
       if (customBackgrounds.selectedTile) {
         customBackgrounds.removeSelectedState(customBackgrounds.selectedTile);
         if (customBackgrounds.selectedTile.id === tile.id) {
@@ -608,7 +608,7 @@
     };
 
     tile.onclick = function(event) {
-      let clickCount = event.detail;
+      const clickCount = event.detail;
       // Control + option + space will fire the onclick event with 0 clickCount.
       if (clickCount <= 1) {
         tileInteraction(event.currentTarget);
@@ -665,7 +665,7 @@
     tileContainer.appendChild(tileBackground);
   }
   let tileGetsLoaded = 0;
-  for (let tile of preLoadTiles) {
+  for (const tile of preLoadTiles) {
     loadTile(tile, collImg, () => {
       // After the preloaded tiles finish loading, the rest of the tiles start
       // loading.
@@ -686,7 +686,7 @@
  * @param {?Function} countLoad If not null, called after the tile finishes
  * loading.
  */
-let loadTile = function(tile, imageData, countLoad) {
+const loadTile = function(tile, imageData, countLoad) {
   if (imageData[tile.dataset.tile_num].collectionId === 'solidcolors') {
     tile.style.backgroundImage = [customBackgrounds.CUSTOM_BACKGROUND_OVERLAY,
       'url(' + imageData[tile.dataset.tile_num].thumbnailImageUrl + ')'].join(
@@ -708,8 +708,8 @@
  * @param {?Function} countLoad If not null, called after the tile finishes
  * loading.
  */
-let fadeInImageTile = function(tile, imageUrl, countLoad) {
-  let image = new Image();
+const fadeInImageTile = function(tile, imageUrl, countLoad) {
+  const image = new Image();
   image.onload = () => {
     tile.style.opacity = '1';
     if (countLoad) {
@@ -725,11 +725,11 @@
  * @private
  */
 customBackgrounds.loadChromeBackgrounds = function() {
-  var collElement = $('ntp-collection-loader');
+  const collElement = $('ntp-collection-loader');
   if (collElement) {
     collElement.parentNode.removeChild(collElement);
   }
-  var collScript = document.createElement('script');
+  const collScript = document.createElement('script');
   collScript.id = 'ntp-collection-loader';
   collScript.src = 'chrome-search://local-ntp/ntp-background-collections.js?' +
       'collection_type=background';
@@ -750,13 +750,13 @@
 customBackgrounds.getNextOption = function(current_index, deltaY) {
   // Create array corresponding to the menu. Important that this is in the same
   // order as the MENU_ENTRIES enum, so we can index into it.
-  var entries = [];
+  const entries = [];
   entries.push($(customBackgrounds.IDS.DEFAULT_WALLPAPERS));
   entries.push($(customBackgrounds.IDS.UPLOAD_IMAGE));
   entries.push($(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT));
   entries.push($(customBackgrounds.IDS.RESTORE_DEFAULT));
 
-  var idx = current_index;
+  let idx = current_index;
   do {
     idx = idx + deltaY;
     if (idx === -1) {
@@ -789,8 +789,8 @@
 customBackgrounds.init = function(
     showErrorNotification, hideCustomLinkNotification) {
   ntpApiHandle = window.chrome.embeddedSearch.newTabPage;
-  let editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
-  let menu = $(customBackgrounds.IDS.MENU);
+  const editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
+  const menu = $(customBackgrounds.IDS.MENU);
 
   $(customBackgrounds.IDS.OPTIONS_TITLE).textContent =
       configData.translatedStrings.customizeBackground;
@@ -803,7 +803,7 @@
       .setAttribute('title', configData.translatedStrings.customizeBackground);
 
   // Edit gear icon interaction events.
-  let editBackgroundInteraction = function() {
+  const editBackgroundInteraction = function() {
     editDialog.showModal();
   };
   $(customBackgrounds.IDS.EDIT_BG).onclick = function(event) {
@@ -812,10 +812,10 @@
   };
 
   // Find the first menu option that is not hidden or disabled.
-  let findFirstMenuOption = () => {
-    let editMenu = $(customBackgrounds.IDS.EDIT_BG_MENU);
+  const findFirstMenuOption = () => {
+    const editMenu = $(customBackgrounds.IDS.EDIT_BG_MENU);
     for (let i = 1; i < editMenu.children.length; i++) {
-      let option = editMenu.children[i];
+      const option = editMenu.children[i];
       if (option.classList.contains(customBackgrounds.CLASSES.OPTION)
           && !option.hidden && !option.classList.contains(
               customBackgrounds.CLASSES.OPTION_DISABLED)) {
@@ -837,7 +837,7 @@
   };
 
   // Interactions to close the customization option dialog.
-  let editDialogInteraction = function() {
+  const editDialogInteraction = function() {
     editDialog.close();
   };
   editDialog.onclick = function(event) {
@@ -885,14 +885,14 @@
 customBackgrounds.initCustomLinksItems = function(hideCustomLinkNotification) {
   customBackgrounds.hideCustomLinkNotification = hideCustomLinkNotification;
 
-  let editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
-  let menu = $(customBackgrounds.IDS.MENU);
+  const editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
+  const menu = $(customBackgrounds.IDS.MENU);
 
   $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT_TEXT).textContent =
       configData.translatedStrings.restoreDefaultLinks;
 
   // Interactions with the "Restore default shortcuts" option.
-  let customLinksRestoreDefaultInteraction = function() {
+  const customLinksRestoreDefaultInteraction = function() {
     editDialog.close();
     customBackgrounds.hideCustomLinkNotification();
     window.chrome.embeddedSearch.newTabPage.resetCustomLinks();
@@ -935,8 +935,8 @@
 customBackgrounds.initCustomBackgrounds = function(showErrorNotification) {
   customBackgrounds.showErrorNotification = showErrorNotification;
 
-  var editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
-  var menu = $(customBackgrounds.IDS.MENU);
+  const editDialog = $(customBackgrounds.IDS.EDIT_BG_DIALOG);
+  const menu = $(customBackgrounds.IDS.MENU);
 
   $(customBackgrounds.IDS.DEFAULT_WALLPAPERS_TEXT).textContent =
       configData.translatedStrings.defaultWallpapers;
@@ -973,7 +973,7 @@
   $(customBackgrounds.IDS.DONE).disabled = true;
 
   // Interactions with the "Upload an image" option.
-  var uploadImageInteraction = function() {
+  const uploadImageInteraction = function() {
     window.chrome.embeddedSearch.newTabPage.selectLocalBackgroundImage();
     ntpApiHandle.logEvent(
         BACKGROUND_CUSTOMIZATION_LOG_TYPE.NTP_CUSTOMIZE_LOCAL_IMAGE_CLICKED);
@@ -1006,7 +1006,7 @@
   };
 
   // Interactions with the "Restore default background" option.
-  var restoreDefaultInteraction = function() {
+  const restoreDefaultInteraction = function() {
     editDialog.close();
     customBackgrounds.clearAttribution();
     window.chrome.embeddedSearch.newTabPage.setBackgroundURL('');
@@ -1040,7 +1040,7 @@
   };
 
   // Interactions with the "Chrome backgrounds" option.
-  var defaultWallpapersInteraction = function(event) {
+  const defaultWallpapersInteraction = function(event) {
     customBackgrounds.loadChromeBackgrounds();
     $('ntp-collection-loader').onload = function() {
       editDialog.close();
@@ -1115,7 +1115,7 @@
   };
 
   // Interactions with the back arrow on the image selection dialog.
-  var backInteraction = function(event) {
+  const backInteraction = function(event) {
     customBackgrounds.resetSelectionDialog();
     customBackgrounds.showCollectionSelectionDialog(
         customBackgrounds.dialogCollectionsSource);
@@ -1150,7 +1150,7 @@
   };
 
   // Interactions with the done button on the background picker dialog.
-  var doneInteraction = function(event) {
+  const doneInteraction = function(event) {
     if ($(customBackgrounds.IDS.DONE).disabled) {
       return;
     }
@@ -1195,13 +1195,13 @@
 };
 
 customBackgrounds.handleError = function(errors) {
-  var unavailableString = configData.translatedStrings.backgroundsUnavailable;
+  const unavailableString = configData.translatedStrings.backgroundsUnavailable;
 
   if (errors != 'undefined') {
     // Network errors.
     if (errors.net_error) {
       if (errors.net_error_no != 0) {
-        let onClick = () => {
+        const onClick = () => {
           window.open(
               'https://chrome://network-error/' + errors.net_error_no,
               '_blank');
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.js b/chrome/browser/resources/local_ntp/custom_links_edit.js
index afb18410..fd6e4d40 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.js
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.js
@@ -56,7 +56,7 @@
  * The prepopulated data for the form. Includes title, url, and rid.
  * @type {Object}
  */
-let prepopulatedLink = {
+const prepopulatedLink = {
   rid: -1,
   title: '',
   url: '',
@@ -96,7 +96,7 @@
   }
 
   // Grab the link data from the embeddedSearch API.
-  let data = chrome.embeddedSearch.newTabPage.getMostVisitedItemData(rid);
+  const data = chrome.embeddedSearch.newTabPage.getMostVisitedItemData(rid);
   if (!data) {
     return;
   }
@@ -117,7 +117,7 @@
  */
 function showInvalidUrlUntilTextInput() {
   $(IDS.URL_FIELD_CONTAINER).classList.add('invalid');
-  let reenable = (event) => {
+  const reenable = (event) => {
     $(IDS.URL_FIELD_CONTAINER).classList.remove('invalid');
     $(IDS.URL_FIELD).removeEventListener('input', reenable);
   };
@@ -197,10 +197,7 @@
  */
 function focusBackOnCancel(event) {
   if (event.keyCode === KEYCODES.ENTER || event.keyCode === KEYCODES.SPACE) {
-    let message = {
-      cmd: 'focusMenu',
-      tid: prepopulatedLink.rid
-    };
+    const message = {cmd: 'focusMenu', tid: prepopulatedLink.rid};
     window.parent.postMessage(message, DOMAIN_ORIGIN);
     event.preventDefault();
     closeDialog();
@@ -222,8 +219,8 @@
  * @param {Event} event Event received.
  */
 function handlePostMessage(event) {
-  let cmd = event.data.cmd;
-  let args = event.data;
+  const cmd = event.data.cmd;
+  const args = event.data;
   if (cmd === 'linkData') {
     if (args.tid) {  // We are editing a link, prepopulate the link data.
       document.title = editLinkTitle;
@@ -254,10 +251,10 @@
  */
 function init() {
   // Parse query arguments.
-  let query = window.location.search.substring(1).split('&');
+  const query = window.location.search.substring(1).split('&');
   queryArgs = {};
   for (let i = 0; i < query.length; ++i) {
-    let val = query[i].split('=');
+    const val = query[i].split('=');
     if (val[0] == '') {
       continue;
     }
@@ -302,7 +299,7 @@
     event.preventDefault();
     finishEditLink();
   });
-  let finishEditOrClose = (event) => {
+  const finishEditOrClose = (event) => {
     if (event.keyCode === KEYCODES.ENTER) {
       event.preventDefault();
       if (!$(IDS.DONE).disabled) {
@@ -319,7 +316,7 @@
   animations.addRippleAnimations();
 
   // Change input field name to blue on input field focus.
-  let changeColor = (fieldTitle) => {
+  const changeColor = (fieldTitle) => {
     $(fieldTitle).classList.toggle('focused');
   };
   $(IDS.TITLE_FIELD)
diff --git a/chrome/browser/resources/local_ntp/doodles.js b/chrome/browser/resources/local_ntp/doodles.js
index bfb601d..77865a37 100644
--- a/chrome/browser/resources/local_ntp/doodles.js
+++ b/chrome/browser/resources/local_ntp/doodles.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 
-let doodles = {};
+const doodles = {};
 
 doodles.numDdllogResponsesReceived = 0;
 doodles.lastDdllogResponse = '';
@@ -116,12 +116,12 @@
  * @param {Object} args, arguments sent to the page via postMessage.
  */
 doodles.resizeDoodleHandler = function(args) {
-  let width = args.width || null;
-  let height = args.height || null;
-  let duration = args.duration || '0s';
-  let iframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
+  const width = args.width || null;
+  const height = args.height || null;
+  const duration = args.duration || '0s';
+  const iframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
 
-  var transitionCallback = function() {
+  const transitionCallback = function() {
     iframe.removeEventListener('webkitTransitionEnd', transitionCallback);
     iframe.contentWindow.postMessage(
         {cmd: 'resizeComplete'}, 'https://www.google.com');
@@ -169,11 +169,11 @@
   });
 
   // Set up doodle notifier (but it may be invisible).
-  var doodleNotifier = $(doodles.IDS.LOGO_DOODLE_NOTIFIER);
+  const doodleNotifier = $(doodles.IDS.LOGO_DOODLE_NOTIFIER);
   doodleNotifier.title = configData.translatedStrings.clickToViewDoodle;
   doodleNotifier.addEventListener('click', function(e) {
     e.preventDefault();
-    var state = window.history.state || {};
+    const state = window.history.state || {};
     state.notheme = true;
     window.history.replaceState(state, document.title);
     ntpApiHandle.logEvent(doodles.LOG_TYPE.NTP_STATIC_LOGO_SHOWN_FROM_CACHE);
@@ -195,7 +195,7 @@
  * @param {function(?{v, usable, image, metadata})} onload
  */
 doodles.loadDoodle = function(v, onload) {
-  var ddlScript = document.createElement('script');
+  const ddlScript = document.createElement('script');
   ddlScript.src = 'chrome-search://local-ntp/doodle.js';
   if (v !== null) {
     ddlScript.src += '?v=' + v;
@@ -224,7 +224,7 @@
       doodles.ei = ddllog.encoded_ei;
     }
     if (ddllog.interaction_log_url) {
-      let interactionLogUrl =
+      const interactionLogUrl =
           new URL(ddllog.interaction_log_url, configData.googleBaseUrl);
       if (isAnimated) {
         doodles.targetDoodle.animatedInteractionLogUrl = interactionLogUrl;
@@ -268,8 +268,9 @@
         if (text.startsWith(preamble)) {
           text = text.substr(preamble.length);
         }
+        let json;
         try {
-          var json = JSON.parse(text);
+          json = JSON.parse(text);
         } catch (error) {
           console.log('Failed to parse doodle impression response as JSON:');
           console.log(error);
@@ -324,9 +325,9 @@
  * @returns {boolean}
  */
 doodles.isDoodleCurrentlyVisible = function() {
-  var haveDoodle = ($(doodles.IDS.LOGO_DOODLE)
-                        .classList.contains(doodles.CLASSES.SHOW_LOGO));
-  var wantDoodle = (doodles.targetDoodle.metadata !== null) &&
+  const haveDoodle = ($(doodles.IDS.LOGO_DOODLE)
+                          .classList.contains(doodles.CLASSES.SHOW_LOGO));
+  const wantDoodle = (doodles.targetDoodle.metadata !== null) &&
       (doodles.targetDoodle.image !== null ||
        doodles.targetDoodle.metadata.type === doodles.LOGO_TYPE.INTERACTIVE);
   if (!haveDoodle || !wantDoodle) {
@@ -335,12 +336,12 @@
 
   // Have a visible doodle and a target doodle. Test that they match.
   if (doodles.targetDoodle.metadata.type === doodles.LOGO_TYPE.INTERACTIVE) {
-    var logoDoodleIframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
+    const logoDoodleIframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
     return logoDoodleIframe.classList.contains(doodles.CLASSES.SHOW_LOGO) &&
         (logoDoodleIframe.src === doodles.targetDoodle.metadata.fullPageUrl);
   } else {
-    var logoDoodleImage = $(doodles.IDS.LOGO_DOODLE_IMAGE);
-    var logoDoodleContainer = $(doodles.IDS.LOGO_DOODLE_CONTAINER);
+    const logoDoodleImage = $(doodles.IDS.LOGO_DOODLE_IMAGE);
+    const logoDoodleContainer = $(doodles.IDS.LOGO_DOODLE_CONTAINER);
     return logoDoodleContainer.classList.contains(doodles.CLASSES.SHOW_LOGO) &&
         ((logoDoodleImage.src === doodles.targetDoodle.image) ||
          (logoDoodleImage.src === doodles.targetDoodle.metadata.animatedUrl));
@@ -365,9 +366,9 @@
 
 
 doodles.getDoodleTargetUrl = function() {
-  let url = new URL(doodles.targetDoodle.metadata.onClickUrl);
+  const url = new URL(doodles.targetDoodle.metadata.onClickUrl);
   if (doodles.targetDoodle.onClickUrlExtraParams) {
-    for (var param of doodles.targetDoodle.onClickUrlExtraParams) {
+    for (const param of doodles.targetDoodle.onClickUrlExtraParams) {
       url.searchParams.append(param[0], param[1]);
     }
   }
@@ -395,8 +396,8 @@
           .classList.remove(doodles.CLASSES.SHOW_LOGO);
 
       // Log the impression in Chrome metrics.
-      var isCta = doodles.targetDoodle.metadata.animatedUrl;
-      var eventType = isCta ?
+      const isCta = doodles.targetDoodle.metadata.animatedUrl;
+      const eventType = isCta ?
           (fromCache ? doodles.LOG_TYPE.NTP_CTA_LOGO_SHOWN_FROM_CACHE :
                        doodles.LOG_TYPE.NTP_CTA_LOGO_SHOWN_FRESH) :
           (fromCache ? doodles.LOG_TYPE.NTP_STATIC_LOGO_SHOWN_FROM_CACHE :
@@ -404,8 +405,8 @@
       ntpApiHandle.logEvent(eventType);
 
       // Ping the proper impression logging URL if it exists.
-      var logUrl = isCta ? doodles.targetDoodle.metadata.ctaLogUrl :
-                           doodles.targetDoodle.metadata.logUrl;
+      const logUrl = isCta ? doodles.targetDoodle.metadata.ctaLogUrl :
+                             doodles.targetDoodle.metadata.logUrl;
       if (logUrl) {
         doodles.logDoodleImpression(logUrl, /*isAnimated=*/ false);
       }
@@ -481,12 +482,12 @@
 
 
 doodles.applyDoodleMetadata = function() {
-  var logoDoodleImage = $(doodles.IDS.LOGO_DOODLE_IMAGE);
-  var logoDoodleButton = $(doodles.IDS.LOGO_DOODLE_BUTTON);
-  var logoDoodleIframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
+  const logoDoodleImage = $(doodles.IDS.LOGO_DOODLE_IMAGE);
+  const logoDoodleButton = $(doodles.IDS.LOGO_DOODLE_BUTTON);
+  const logoDoodleIframe = $(doodles.IDS.LOGO_DOODLE_IFRAME);
 
-  var logoDoodleShareButton = null;
-  var logoDoodleShareDialog = null;
+  const logoDoodleShareButton = null;
+  const logoDoodleShareDialog = null;
 
   switch (doodles.targetDoodle.metadata.type) {
     case doodles.LOGO_TYPE.SIMPLE:
@@ -586,11 +587,11 @@
       !doodles.targetDoodle.metadata.shareButtonIcon) {
     return;
   }
-  var shareDialog = $(doodles.IDS.DOODLE_SHARE_DIALOG);
+  const shareDialog = $(doodles.IDS.DOODLE_SHARE_DIALOG);
 
-  var shareButtonWrapper = document.createElement('button');
+  const shareButtonWrapper = document.createElement('button');
   shareButtonWrapper.id = doodles.IDS.DOODLE_SHARE_BUTTON;
-  var shareButtonImg = document.createElement('img');
+  const shareButtonImg = document.createElement('img');
   shareButtonImg.id = doodles.IDS.DOODLE_SHARE_BUTTON_IMG;
   shareButtonWrapper.appendChild(shareButtonImg);
   shareButtonWrapper.title = configData.translatedStrings.shareDoodle;
@@ -604,10 +605,10 @@
   // Share button opacity represented as a double between 0 to 1.
   // Final background color is an RGBA HEX string created by combining
   // both.
-  var backgroundColor = doodles.targetDoodle.metadata.shareButtonBg;
+  let backgroundColor = doodles.targetDoodle.metadata.shareButtonBg;
   if (!!doodles.targetDoodle.metadata.shareButtonOpacity ||
       doodles.targetDoodle.metadata.shareButtonOpacity == 0) {
-    var backgroundOpacityHex =
+    const backgroundOpacityHex =
         parseInt(doodles.targetDoodle.metadata.shareButtonOpacity * 255, 10)
             .toString(16);
     backgroundColor += backgroundOpacityHex;
@@ -620,12 +621,12 @@
     shareDialog.showModal();
   };
 
-  var oldButton = $(doodles.IDS.DOODLE_SHARE_BUTTON);
+  const oldButton = $(doodles.IDS.DOODLE_SHARE_BUTTON);
   if (oldButton) {
     oldButton.remove();
   }
 
-  var logoContainer = $(doodles.IDS.LOGO_DOODLE_CONTAINER);
+  const logoContainer = $(doodles.IDS.LOGO_DOODLE_CONTAINER);
   if (logoContainer) {
     logoContainer.appendChild(shareButtonWrapper);
   }
@@ -636,14 +637,14 @@
  * title and short link.
  */
 doodles.updateShareDialog = function() {
-  var shareDialog = $(doodles.IDS.DOODLE_SHARE_DIALOG);
-  var shareDialogTitle = $(doodles.IDS.DOODLE_SHARE_DIALOG_TITLE);
-  var closeButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_CLOSE_BUTTON);
-  var facebookButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_FACEBOOK_BUTTON);
-  var twitterButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_TWITTER_BUTTON);
-  var mailButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_MAIL_BUTTON);
-  var copyButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_COPY_LINK_BUTTON);
-  var linkText = $(doodles.IDS.DOODLE_SHARE_DIALOG_LINK);
+  const shareDialog = $(doodles.IDS.DOODLE_SHARE_DIALOG);
+  const shareDialogTitle = $(doodles.IDS.DOODLE_SHARE_DIALOG_TITLE);
+  const closeButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_CLOSE_BUTTON);
+  const facebookButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_FACEBOOK_BUTTON);
+  const twitterButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_TWITTER_BUTTON);
+  const mailButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_MAIL_BUTTON);
+  const copyButton = $(doodles.IDS.DOODLE_SHARE_DIALOG_COPY_LINK_BUTTON);
+  const linkText = $(doodles.IDS.DOODLE_SHARE_DIALOG_LINK);
 
   if (!doodles.targetDoodle.metadata ||
       !doodles.targetDoodle.metadata.shortLink ||
@@ -651,7 +652,7 @@
     return;
   }
 
-  var closeDialog = function() {
+  const closeDialog = function() {
     shareDialog.close();
   };
 
@@ -663,13 +664,13 @@
     }
   };
 
-  var title = doodles.targetDoodle.metadata.altText;
+  const title = doodles.targetDoodle.metadata.altText;
 
   shareDialogTitle.innerHTML = title;
-  var shortLink = doodles.targetDoodle.metadata.shortLink;
+  const shortLink = doodles.targetDoodle.metadata.shortLink;
 
   facebookButton.onclick = function() {
-    var url = 'https://www.facebook.com/dialog/share' +
+    const url = 'https://www.facebook.com/dialog/share' +
         '?app_id=' + doodles.FACEBOOK_APP_ID +
         '&href=' + encodeURIComponent(shortLink) +
         '&hashtag=' + encodeURIComponent('#GoogleDoodle');
@@ -679,7 +680,7 @@
   facebookButton.title = configData.translatedStrings.shareFacebook;
 
   twitterButton.onclick = function() {
-    var url = 'https://twitter.com/intent/tweet' +
+    const url = 'https://twitter.com/intent/tweet' +
         '?text=' + encodeURIComponent(title + '\n' + shortLink);
     window.open(url);
     doodles.logDoodleShare(doodles.SHARE_TYPE.TWITTER);
@@ -687,7 +688,7 @@
   twitterButton.title = configData.translatedStrings.shareTwitter;
 
   mailButton.onclick = function() {
-    var url = 'mailto:?subject=' + encodeURIComponent(title) +
+    const url = 'mailto:?subject=' + encodeURIComponent(title) +
         '&body=' + encodeURIComponent(shortLink);
     document.location.href = url;
     doodles.logDoodleShare(doodles.SHARE_TYPE.EMAIL);
diff --git a/chrome/browser/resources/local_ntp/instant_iframe_validation.js b/chrome/browser/resources/local_ntp/instant_iframe_validation.js
index a557dc6..e0a684d 100644
--- a/chrome/browser/resources/local_ntp/instant_iframe_validation.js
+++ b/chrome/browser/resources/local_ntp/instant_iframe_validation.js
@@ -18,7 +18,7 @@
   // range for an RGB hex color.
   if (isFinite(color) && Math.floor(color) == color && color >= 0 &&
       color <= 0xffffff) {
-    var hexColor = color.toString(16);
+    const hexColor = color.toString(16);
     // Pads with initial zeros and # (e.g. for 'ff' yields '#0000ff').
     return '#000000'.substr(0, 7 - hexColor.length) + hexColor;
   }
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 40f5091..ca8fe9a 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -14,7 +14,7 @@
  * has completed.
  * @type {boolean}
  */
-var tilesAreLoaded = false;
+let tilesAreLoaded = false;
 
 
 /**
@@ -57,7 +57,7 @@
  *   titleColorAgainstDark: Array<number>,
  * }}
  */
-var NTP_DESIGN = {
+const NTP_DESIGN = {
   numTitleLines: 1,
   titleColor: [50, 50, 50, 255],
   titleColorAgainstDark: [210, 210, 210, 255],
@@ -69,7 +69,7 @@
  * @enum {string}
  * @const
  */
-var CLASSES = {
+const CLASSES = {
   // Shows a Google search style fakebox.
   ALTERNATE_FAKEBOX: 'alternate-fakebox',
   // Shows a rectangular Google search style fakebox with rounded corners.
@@ -116,7 +116,7 @@
  * @enum {string}
  * @const
  */
-var IDS = {
+const IDS = {
   ATTRIBUTION: 'attribution',
   ATTRIBUTION_TEXT: 'attribution-text',
   CUSTOM_BG: 'custom-bg',
@@ -153,7 +153,7 @@
  * @enum {string}
  * @const
  */
-var LOGO_TYPE = {
+const LOGO_TYPE = {
   SIMPLE: 'SIMPLE',
   ANIMATED: 'ANIMATED',
   INTERACTIVE: 'INTERACTIVE',
@@ -168,7 +168,7 @@
  * @enum {number}
  * @const
  */
-var LOG_TYPE = {
+const LOG_TYPE = {
   // A static Doodle was shown, coming from cache.
   NTP_STATIC_LOGO_SHOWN_FROM_CACHE: 30,
   // A static Doodle was shown, coming from the network.
@@ -224,7 +224,7 @@
  * @type {Array<string>}
  * @const
  */
-var WHITE_BACKGROUND_COLORS = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
+const WHITE_BACKGROUND_COLORS = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
 
 
 /**
@@ -241,7 +241,7 @@
  * @enum {number}
  * @const
  */
-var KEYCODE = {ENTER: 13, SPACE: 32};
+const KEYCODE = {ENTER: 13, SPACE: 32};
 
 
 /**
@@ -264,7 +264,7 @@
  * filler.
  * @type {?number}
  */
-var lastBlacklistedTile = null;
+let lastBlacklistedTile = null;
 
 
 /**
@@ -287,7 +287,7 @@
  * The browser embeddedSearch.newTabPage object.
  * @type {Object}
  */
-var ntpApiHandle;
+let ntpApiHandle;
 
 
 /**
@@ -313,7 +313,7 @@
  * @return {Object}
  */
 function createExecutableTimeout(timeout, delay) {
-  let timeoutId = window.setTimeout(() => {
+  const timeoutId = window.setTimeout(() => {
     timeout(/*executedEarly=*/ false);
   }, delay);
   return {
@@ -371,7 +371,7 @@
  * @private
  */
 function getIsThemeDark() {
-  var info = getThemeBackgroundInfo();
+  const info = getThemeBackgroundInfo();
   // Only check for dark mode if this is the default NTP (i.e. no theme or
   // custom background set).
   if (!info || info.usingDefaultTheme && !info.customBackgroundConfigured) {
@@ -380,8 +380,8 @@
   }
 
   // Heuristic: light text implies dark theme.
-  var rgba = info.textColorRgba;
-  var luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
+  const rgba = info.textColorRgba;
+  const luminance = 0.3 * rgba[0] + 0.59 * rgba[1] + 0.11 * rgba[2];
   return luminance >= 128;
 }
 
@@ -407,7 +407,7 @@
 function renderTheme() {
   $(IDS.NTP_CONTENTS).classList.toggle(CLASSES.DARK, getIsThemeDark());
 
-  var info = getThemeBackgroundInfo();
+  const info = getThemeBackgroundInfo();
   if (!info) {
     return;
   }
@@ -418,7 +418,7 @@
   document.documentElement.setAttribute('darkmode', isDarkModeEnabled);
   document.body.classList.toggle('light-chip', !useDarkChips);
 
-  var background = [
+  const background = [
     convertToRGBAColor(info.backgroundColorRgba), info.imageUrl,
     info.imageTiling, info.imageHorizontalAlignment, info.imageVerticalAlignment
   ].join(' ').trim();
@@ -447,7 +447,7 @@
   setCustomThemeStyle(info);
 
   if (info.customBackgroundConfigured) {
-    var imageWithOverlay = [
+    const imageWithOverlay = [
       customBackgrounds.CUSTOM_BACKGROUND_OVERLAY, 'url(' + info.imageUrl + ')'
     ].join(',').trim();
 
@@ -460,7 +460,7 @@
     // to display the custom background until |image| is fully loaded ensures
     // that |imageWithOverlay| is also loaded.
     $(IDS.CUSTOM_BG).style.backgroundImage = imageWithOverlay;
-    var image = new Image();
+    const image = new Image();
     image.onload = function() {
       $(IDS.CUSTOM_BG).style.opacity = '1';
     };
@@ -500,19 +500,19 @@
  * @private
  */
 function sendThemeInfoToMostVisitedIframe() {
-  var info = getThemeBackgroundInfo();
+  const info = getThemeBackgroundInfo();
   if (!info) {
     return;
   }
 
-  var isThemeDark = getIsThemeDark();
+  const isThemeDark = getIsThemeDark();
 
-  var message = {cmd: 'updateTheme'};
+  const message = {cmd: 'updateTheme'};
   message.isThemeDark = isThemeDark;
   message.isUsingTheme = !info.usingDefaultTheme;
   message.isDarkMode = getUseDarkChips(info);
 
-  var titleColor = NTP_DESIGN.titleColor;
+  let titleColor = NTP_DESIGN.titleColor;
   if (!info.usingDefaultTheme && info.textColorRgba) {
     titleColor = info.textColorRgba;
   } else if (isThemeDark) {
@@ -538,7 +538,7 @@
     return;
   }
 
-  let message = {cmd: 'updateTheme'};
+  const message = {cmd: 'updateTheme'};
   message.isDarkMode = info.usingDarkMode;
 
   $(IDS.CUSTOM_LINKS_EDIT_IFRAME).contentWindow.postMessage(message, '*');
@@ -555,11 +555,11 @@
     return;
   }
   try {
-    var oneGoogleBarApi = window.gbar.a;
-    var oneGoogleBarPromise = oneGoogleBarApi.bf();
+    const oneGoogleBarApi = window.gbar.a;
+    const oneGoogleBarPromise = oneGoogleBarApi.bf();
     oneGoogleBarPromise.then(function(oneGoogleBar) {
-      var isThemeDark = getIsThemeDark();
-      var setForegroundStyle = oneGoogleBar.pc.bind(oneGoogleBar);
+      const isThemeDark = getIsThemeDark();
+      const setForegroundStyle = oneGoogleBar.pc.bind(oneGoogleBar);
       setForegroundStyle(isThemeDark ? 1 : 0);
     });
   } catch (err) {
@@ -595,9 +595,9 @@
  * @private
  */
 function setCustomThemeStyle(themeInfo) {
-  var textColor = '';
-  var textColorLight = '';
-  var mvxFilter = '';
+  let textColor = '';
+  let textColorLight = '';
+  let mvxFilter = '';
   if (!themeInfo.usingDefaultTheme) {
     textColor = convertToRGBAColor(themeInfo.textColorRgba);
     textColorLight = convertToRGBAColor(themeInfo.textColorLightRgba);
@@ -627,8 +627,8 @@
     return;
   }
 
-  var attribution = $(IDS.ATTRIBUTION);
-  var attributionImage = attribution.querySelector('img');
+  const attribution = $(IDS.ATTRIBUTION);
+  let attributionImage = attribution.querySelector('img');
   if (!attributionImage) {
     attributionImage = new Image();
     attribution.appendChild(attributionImage);
@@ -688,11 +688,11 @@
     return;
   }
 
-  var pages = ntpApiHandle.mostVisited;
-  var cmds = [];
-  let maxNumTiles = configData.isGooglePage ? MAX_NUM_TILES_CUSTOM_LINKS :
-                                              MAX_NUM_TILES_MOST_VISITED;
-  for (var i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
+  const pages = ntpApiHandle.mostVisited;
+  const cmds = [];
+  const maxNumTiles = configData.isGooglePage ? MAX_NUM_TILES_CUSTOM_LINKS :
+                                                MAX_NUM_TILES_MOST_VISITED;
+  for (let i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
     cmds.push({cmd: 'tile', rid: pages[i].rid, darkMode: useDarkChips});
   }
   cmds.push({cmd: 'show'});
@@ -780,10 +780,10 @@
  * @param {?Function} linkOnClick The error link onclick handler.
  */
 function showErrorNotification(msg, linkName, linkOnClick) {
-  let notification = $(IDS.ERROR_NOTIFICATION);
+  const notification = $(IDS.ERROR_NOTIFICATION);
   $(IDS.ERROR_NOTIFICATION_MSG).textContent = msg;
   if (linkName && linkOnClick) {
-    let notificationLink = $(IDS.ERROR_NOTIFICATION_LINK);
+    const notificationLink = $(IDS.ERROR_NOTIFICATION_LINK);
     notificationLink.textContent = linkName;
     notificationLink.onclick = linkOnClick;
     notification.classList.add(CLASSES.HAS_LINK);
@@ -820,7 +820,7 @@
   }
 
   // Hide middle-slot promo if one is present.
-  let promo = $(IDS.PROMO);
+  const promo = $(IDS.PROMO);
   if (promo) {
     promo.classList.add(CLASSES.FLOAT_DOWN);
     // Prevent keyboard focus once the promo is hidden.
@@ -875,7 +875,7 @@
 
   if (showPromo) {
     // Show middle-slot promo if one is present.
-    let promo = $(IDS.PROMO);
+    const promo = $(IDS.PROMO);
     if (promo) {
       promo.classList.remove(CLASSES.HIDE_NOTIFICATION);
       // Timeout is required for the "float" transition to work. Modifying the
@@ -1020,8 +1020,8 @@
  * @param {Event} event Event received.
  */
 function handlePostMessage(event) {
-  var cmd = event.data.cmd;
-  var args = event.data;
+  const cmd = event.data.cmd;
+  const args = event.data;
   if (cmd === 'loaded') {
     tilesAreLoaded = true;
     if (configData.isGooglePage) {
@@ -1034,7 +1034,7 @@
         // "og" which is a dict corresponding to the native OneGoogleBarData
         // type. We do this only after all the tiles have loaded, to avoid
         // slowing down the main page load.
-        var ogScript = document.createElement('script');
+        const ogScript = document.createElement('script');
         ogScript.id = 'one-google-loader';
         ogScript.src = 'chrome-search://local-ntp/one-google.js';
         document.body.appendChild(ogScript);
@@ -1043,7 +1043,7 @@
         };
       }
       if (!$('promo-loader')) {
-        var promoScript = document.createElement('script');
+        const promoScript = document.createElement('script');
         promoScript.id = 'promo-loader';
         promoScript.src = 'chrome-search://local-ntp/promo.js';
         document.body.appendChild(promoScript);
@@ -1093,7 +1093,7 @@
   // Inject search suggestions as early as possible to avoid shifting of other
   // elements.
   if (!$('search-suggestions-loader')) {
-    var ssScript = document.createElement('script');
+    const ssScript = document.createElement('script');
     ssScript.id = 'search-suggestions-loader';
     ssScript.src = 'chrome-search://local-ntp/search-suggestions.js';
     ssScript.async = false;
@@ -1126,13 +1126,13 @@
   $(IDS.NOTIFICATION_MESSAGE).textContent =
       configData.translatedStrings.thumbnailRemovedNotification;
 
-  var undoLink = $(IDS.UNDO_LINK);
+  const undoLink = $(IDS.UNDO_LINK);
   undoLink.addEventListener('click', onUndo);
   registerKeyHandler(undoLink, KEYCODE.ENTER, onUndo);
   registerKeyHandler(undoLink, KEYCODE.SPACE, onUndo);
   undoLink.textContent = configData.translatedStrings.undoThumbnailRemove;
 
-  var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
+  const restoreAllLink = $(IDS.RESTORE_ALL_LINK);
   restoreAllLink.addEventListener('click', onRestoreAll);
   registerKeyHandler(restoreAllLink, KEYCODE.ENTER, onRestoreAll);
   registerKeyHandler(restoreAllLink, KEYCODE.SPACE, onRestoreAll);
@@ -1144,7 +1144,7 @@
   $(IDS.ATTRIBUTION_TEXT).textContent =
       configData.translatedStrings.attributionIntro;
 
-  var embeddedSearchApiHandle = window.chrome.embeddedSearch;
+  const embeddedSearchApiHandle = window.chrome.embeddedSearch;
 
   ntpApiHandle = embeddedSearchApiHandle.newTabPage;
   ntpApiHandle.onthemechange = onThemeChange;
@@ -1152,7 +1152,7 @@
 
   renderTheme();
 
-  var searchboxApiHandle = embeddedSearchApiHandle.searchBox;
+  const searchboxApiHandle = embeddedSearchApiHandle.searchBox;
 
   if (configData.isGooglePage) {
     showSearchSuggestions();
@@ -1211,18 +1211,18 @@
       searchboxApiHandle.onkeycapturechange = function() {
         setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
       };
-      var inputbox = $(IDS.FAKEBOX_INPUT);
+      const inputbox = $(IDS.FAKEBOX_INPUT);
       inputbox.onpaste = function(event) {
         event.preventDefault();
         // Send pasted text to Omnibox.
-        var text = event.clipboardData.getData('text/plain');
+        const text = event.clipboardData.getData('text/plain');
         if (text) {
           searchboxApiHandle.paste(text);
         }
       };
       inputbox.ondrop = function(event) {
         event.preventDefault();
-        var text = event.dataTransfer.getData('text/plain');
+        const text = event.dataTransfer.getData('text/plain');
         if (text) {
           searchboxApiHandle.paste(text);
         }
@@ -1277,9 +1277,9 @@
  */
 function createIframes() {
   // Collect arguments for the most visited iframe.
-  var args = [];
+  const args = [];
 
-  var searchboxApiHandle = window.chrome.embeddedSearch.searchBox;
+  const searchboxApiHandle = window.chrome.embeddedSearch.searchBox;
 
   if (searchboxApiHandle.rtl) {
     args.push('rtl=1');
@@ -1308,7 +1308,7 @@
   }
 
   // Create the most visited iframe.
-  var iframe = document.createElement('iframe');
+  const iframe = document.createElement('iframe');
   iframe.id = IDS.TILES_IFRAME;
   iframe.name = IDS.TILES_IFRAME;
   iframe.title = configData.translatedStrings.mostVisitedTitle;
@@ -1322,7 +1322,7 @@
 
   if (configData.isGooglePage) {
     // Collect arguments for the edit custom link iframe.
-    let clArgs = [];
+    const clArgs = [];
 
     if (searchboxApiHandle.rtl) {
       clArgs.push('rtl=1');
@@ -1354,12 +1354,12 @@
         encodeURIComponent(configData.translatedStrings.invalidUrl));
 
     // Create the edit custom link iframe.
-    let clIframe = document.createElement('iframe');
+    const clIframe = document.createElement('iframe');
     clIframe.id = IDS.CUSTOM_LINKS_EDIT_IFRAME;
     clIframe.name = IDS.CUSTOM_LINKS_EDIT_IFRAME;
     clIframe.title = configData.translatedStrings.editLinkTitle;
     clIframe.src = 'chrome-search://most-visited/edit.html?' + clArgs.join('&');
-    let clIframeDialog = document.createElement('dialog');
+    const clIframeDialog = document.createElement('dialog');
     clIframeDialog.id = IDS.CUSTOM_LINKS_EDIT_IFRAME_DIALOG;
     clIframeDialog.classList.add(CLASSES.CUSTOMIZE_DIALOG);
     clIframeDialog.appendChild(clIframe);
@@ -1396,7 +1396,7 @@
     return;
   }
 
-  let promoContainer = document.createElement('div');
+  const promoContainer = document.createElement('div');
   promoContainer.id = IDS.PROMO;
   promoContainer.innerHTML += promo.promoHtml;
   $(IDS.NTP_CONTENTS).appendChild(promoContainer);
@@ -1407,7 +1407,7 @@
 
   ntpApiHandle.logEvent(LOG_TYPE.NTP_MIDDLE_SLOT_PROMO_SHOWN);
 
-  let links = promoContainer.getElementsByTagName('a');
+  const links = promoContainer.getElementsByTagName('a');
   if (links[0]) {
     links[0].onclick = function() {
       ntpApiHandle.logEvent(LOG_TYPE.NTP_MIDDLE_SLOT_PROMO_LINK_CLICKED);
@@ -1425,13 +1425,13 @@
     return;
   }
 
-  let suggestionsContainer = document.createElement('div');
+  const suggestionsContainer = document.createElement('div');
   suggestionsContainer.id = IDS.SUGGESTIONS;
   suggestionsContainer.style.visibility = 'hidden';
   suggestionsContainer.innerHTML += suggestions.suggestionsHtml;
   $(IDS.USER_CONTENT).insertAdjacentElement('afterbegin', suggestionsContainer);
 
-  let endOfBodyScript = document.createElement('script');
+  const endOfBodyScript = document.createElement('script');
   endOfBodyScript.type = 'text/javascript';
   endOfBodyScript.appendChild(
       document.createTextNode(suggestions.suggestionsEndOfBodyScript));
@@ -1444,30 +1444,30 @@
  * doesn't block the main page load.
  */
 function injectOneGoogleBar(ogb) {
-  var inHeadStyle = document.createElement('style');
+  const inHeadStyle = document.createElement('style');
   inHeadStyle.type = 'text/css';
   inHeadStyle.appendChild(document.createTextNode(ogb.inHeadStyle));
   document.head.appendChild(inHeadStyle);
 
-  var inHeadScript = document.createElement('script');
+  const inHeadScript = document.createElement('script');
   inHeadScript.type = 'text/javascript';
   inHeadScript.appendChild(document.createTextNode(ogb.inHeadScript));
   document.head.appendChild(inHeadScript);
 
   renderOneGoogleBarTheme();
 
-  var ogElem = $('one-google');
+  const ogElem = $('one-google');
   ogElem.innerHTML = ogb.barHtml;
   ogElem.classList.remove('hidden');
 
-  var afterBarScript = document.createElement('script');
+  const afterBarScript = document.createElement('script');
   afterBarScript.type = 'text/javascript';
   afterBarScript.appendChild(document.createTextNode(ogb.afterBarScript));
   ogElem.parentNode.insertBefore(afterBarScript, ogElem.nextSibling);
 
   $('one-google-end-of-body').innerHTML = ogb.endOfBodyHtml;
 
-  var endOfBodyScript = document.createElement('script');
+  const endOfBodyScript = document.createElement('script');
   endOfBodyScript.type = 'text/javascript';
   endOfBodyScript.appendChild(document.createTextNode(ogb.endOfBodyScript));
   document.body.appendChild(endOfBodyScript);
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js
index bc8a6834..ca1da432 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -71,7 +71,7 @@
  * @enum {number}
  * @const
  */
-var LOG_TYPE = {
+const LOG_TYPE = {
   // All NTP tiles have finished loading (successfully or failing).
   NTP_ALL_TILES_LOADED: 11,
   // The data for all NTP tiles (title, URL, etc, but not the thumbnail image)
@@ -95,7 +95,7 @@
  * @enum {number}
  * @const
  */
-var TileVisualType = {
+const TileVisualType = {
   NONE: 0,
   ICON_REAL: 1,
   ICON_COLOR: 2,
@@ -164,7 +164,7 @@
  * at 1 because initially we're waiting for the "show" message from the parent.
  * @type {number}
  */
-var loadedCounter = 1;
+let loadedCounter = 1;
 
 
 /**
@@ -172,7 +172,7 @@
  * Works as a double-buffer that is shown when we receive a "show" postMessage.
  * @type {Element}
  */
-var tiles = null;
+let tiles = null;
 
 
 /**
@@ -189,7 +189,7 @@
  * List of parameters passed by query args.
  * @type {Object}
  */
-var queryArgs = {};
+let queryArgs = {};
 
 
 /**
@@ -218,9 +218,9 @@
  * Log an event on the NTP.
  * @param {number} eventType Event from LOG_TYPE.
  */
-var logEvent = function(eventType) {
+function logEvent(eventType) {
   chrome.embeddedSearch.newTabPage.logEvent(eventType);
-};
+}
 
 /**
  * Log impression of an NTP tile.
@@ -261,7 +261,7 @@
  * When we get to 0, we send a message to the parent window.
  * This is usually used as an EventListener of onload/onerror.
  */
-var countLoad = function() {
+function countLoad() {
   loadedCounter -= 1;
   if (loadedCounter <= 0) {
     swapInNewTiles();
@@ -284,22 +284,22 @@
     // fresh tiles.
     loadedCounter = 1;
   }
-};
+}
 
 
 /**
  * Handles postMessages coming from the host page to the iframe.
  * Mostly, it dispatches every command to handleCommand.
  */
-var handlePostMessage = function(event) {
+function handlePostMessage(event) {
   if (event.data instanceof Array) {
-    for (var i = 0; i < event.data.length; ++i) {
+    for (let i = 0; i < event.data.length; ++i) {
       handleCommand(event.data[i]);
     }
   } else {
     handleCommand(event.data);
   }
-};
+}
 
 
 /**
@@ -307,8 +307,8 @@
  * We try to keep the logic here to a minimum and just dispatch to the relevant
  * functions.
  */
-var handleCommand = function(data) {
-  var cmd = data.cmd;
+function handleCommand(data) {
+  const cmd = data.cmd;
 
   if (cmd == 'tile') {
     addTile(data);
@@ -324,25 +324,25 @@
   } else {
     console.error('Unknown command: ' + JSON.stringify(data));
   }
-};
+}
 
 
 /**
  * Handler for the 'show' message from the host page.
  * @param {!Object} info Data received in the message.
  */
-var showTiles = function(info) {
+function showTiles(info) {
   logEvent(LOG_TYPE.NTP_ALL_TILES_RECEIVED);
   utils.setPlatformClass(document.body);
   countLoad();
-};
+}
 
 
 /**
  * Handler for the 'updateTheme' message from the host page.
  * @param {!Object} info Data received in the message.
  */
-var updateTheme = function(info) {
+function updateTheme(info) {
   document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
   document.body.classList.toggle('dark-theme', info.isThemeDark);
   document.body.classList.toggle('using-theme', info.isUsingTheme);
@@ -354,7 +354,7 @@
       !info.isThemeDark && !info.isUsingTheme &&
           (navigator.userAgent.indexOf('Mac') > -1 ||
            navigator.userAgent.indexOf('CrOS') > -1));
-};
+}
 
 
 /**
@@ -363,26 +363,26 @@
  * without saving.
  * @param {!Object} info Data received in the message.
  */
-var focusTileMenu = function(info) {
-  let tile = document.querySelector(`a.md-tile[data-tid="${info.tid}"]`);
+function focusTileMenu(info) {
+  const tile = document.querySelector(`a.md-tile[data-tid="${info.tid}"]`);
   if (info.tid === -1 /* Add shortcut tile */) {
     tile.focus();
   } else {
     tile.parentNode.childNodes[1].focus();
   }
-};
+}
 
 
 /**
  * Removes all old instances of |IDS.MV_TILES| that are pending for deletion.
  */
-var removeAllOldTiles = function() {
-  var parent = document.querySelector('#' + IDS.MOST_VISITED);
-  var oldList = parent.querySelectorAll('.mv-tiles-old');
-  for (var i = 0; i < oldList.length; ++i) {
+function removeAllOldTiles() {
+  const parent = document.querySelector('#' + IDS.MOST_VISITED);
+  const oldList = parent.querySelectorAll('.mv-tiles-old');
+  for (let i = 0; i < oldList.length; ++i) {
     parent.removeChild(oldList[i]);
   }
-};
+}
 
 
 /**
@@ -390,14 +390,14 @@
  * their thumbnail images, and we are ready to show the new tiles and drop the
  * old ones.
  */
-var swapInNewTiles = function() {
+function swapInNewTiles() {
   // Store the tiles on the current closure.
-  var cur = tiles;
+  const cur = tiles;
 
   // Add an "add new custom link" button if we haven't reached the maximum
   // number of tiles.
   if (isCustomLinksEnabled && cur.childNodes.length < maxNumTiles) {
-    let data = {
+    const data = {
       'tid': -1,
       'title': queryArgs['addLink'],
       'url': '',
@@ -409,11 +409,11 @@
     tiles.appendChild(renderMaterialDesignTile(data));
   }
 
-  var parent = document.querySelector('#' + IDS.MOST_VISITED);
+  const parent = document.querySelector('#' + IDS.MOST_VISITED);
 
   // Only fade in the new tiles if there were tiles before.
-  var fadeIn = false;
-  var old = parent.querySelector('#' + IDS.MV_TILES);
+  let fadeIn = false;
+  const old = parent.querySelector('#' + IDS.MV_TILES);
   if (old) {
     fadeIn = true;
     // Mark old tile DIV for removal after the transition animation is done.
@@ -451,7 +451,7 @@
   // Make sure the tiles variable contain the next tileset we'll use if the host
   // page sends us an updated set of tiles.
   tiles = document.createElement('div');
-};
+}
 
 
 /**
@@ -480,10 +480,10 @@
  * It's also used to fill up our tiles to |maxNumTiles| if necessary.
  * @param {?MostVisitedData} args Data for the tile to be rendered.
  */
-var addTile = function(args) {
+function addTile(args) {
   if (isFinite(args.rid)) {
     // An actual suggestion. Grab the data from the embeddedSearch API.
-    var data =
+    const data =
         chrome.embeddedSearch.newTabPage.getMostVisitedItemData(args.rid);
     if (!data) {
       return;
@@ -501,7 +501,7 @@
     // An empty tile
     tiles.appendChild(renderTile(null));
   }
-};
+}
 
 /**
  * Called when the user decided to add a tile to the blacklist.
@@ -509,8 +509,8 @@
  * to the host page.
  * @param {Element} tile DOM node of the tile we want to remove.
  */
-var blacklistTile = function(tile) {
-  let tid = Number(tile.firstChild.getAttribute('data-tid'));
+function blacklistTile(tile) {
+  const tid = Number(tile.firstChild.getAttribute('data-tid'));
 
   if (isCustomLinksEnabled) {
     chrome.embeddedSearch.newTabPage.deleteMostVisitedItem(tid);
@@ -524,7 +524,7 @@
           {cmd: 'tileBlacklisted', tid: Number(tid)}, DOMAIN_ORIGIN);
     });
   }
-};
+}
 
 
 /**
@@ -597,19 +597,19 @@
 
       // Cancel the timeout if the user drags the mouse off the tile and
       // releases or if the mouse if released.
-      let dragend = () => {
+      const dragend = () => {
         window.clearTimeout(timeout);
       };
       document.addEventListener('dragend', dragend, {once: true});
 
-      let mouseup = () => {
+      const mouseup = () => {
         if (event.button == 0 /* LEFT CLICK */) {
           window.clearTimeout(timeout);
         }
       };
       document.addEventListener('mouseup', mouseup, {once: true});
 
-      let timeoutFunc = (dragend_in, mouseup_in) => {
+      const timeoutFunc = (dragend_in, mouseup_in) => {
         if (!reordering) {
           startReorder(tile);
         }
@@ -652,9 +652,9 @@
  *     construct an empty tile. isAddButton can only be set if custom links is
  *     enabled.
  */
-var renderTile = function(data) {
+function renderTile(data) {
   return renderMaterialDesignTile(data);
-};
+}
 
 
 /**
@@ -666,7 +666,7 @@
  * @return {Element}
  */
 function renderMaterialDesignTile(data) {
-  let mdTileContainer = document.createElement('div');
+  const mdTileContainer = document.createElement('div');
   mdTileContainer.role = 'none';
 
   if (data == null) {
@@ -680,7 +680,7 @@
   // This is set in the load/error event for the favicon image.
   let tileType = TileVisualType.NONE;
 
-  let mdTile = document.createElement('a');
+  const mdTile = document.createElement('a');
   mdTile.className = CLASSES.MD_TILE;
   mdTile.tabIndex = 0;
   mdTile.setAttribute('data-tid', data.tid);
@@ -726,22 +726,22 @@
   });
   utils.disableOutlineOnMouseClick(mdTile);
 
-  let mdTileInner = document.createElement('div');
+  const mdTileInner = document.createElement('div');
   mdTileInner.className = CLASSES.MD_TILE_INNER;
 
-  let mdIcon = document.createElement('div');
+  const mdIcon = document.createElement('div');
   mdIcon.className = CLASSES.MD_ICON;
 
   if (data.isAddButton) {
-    let mdAdd = document.createElement('div');
+    const mdAdd = document.createElement('div');
     mdAdd.className = CLASSES.MD_ADD_ICON;
-    let addBackground = document.createElement('div');
+    const addBackground = document.createElement('div');
     addBackground.className = CLASSES.MD_ICON_BACKGROUND;
 
     addBackground.appendChild(mdAdd);
     mdIcon.appendChild(addBackground);
   } else {
-    let fi = document.createElement('img');
+    const fi = document.createElement('img');
     // Set title and alt to empty so screen readers won't say the image name.
     fi.title = '';
     fi.alt = '';
@@ -763,9 +763,9 @@
       countLoad();
     });
     fi.addEventListener('error', function(ev) {
-      let fallbackBackground = document.createElement('div');
+      const fallbackBackground = document.createElement('div');
       fallbackBackground.className = CLASSES.MD_ICON_BACKGROUND;
-      let fallbackLetter = document.createElement('div');
+      const fallbackLetter = document.createElement('div');
       fallbackLetter.className = CLASSES.MD_FALLBACK_LETTER;
       fallbackLetter.textContent = data.title.charAt(0).toUpperCase();
       mdIcon.classList.add(CLASSES.FAILED_FAVICON);
@@ -790,11 +790,11 @@
 
   mdTileInner.appendChild(mdIcon);
 
-  let mdTitleContainer = document.createElement('div');
+  const mdTitleContainer = document.createElement('div');
   mdTitleContainer.className = CLASSES.MD_TITLE_CONTAINER;
-  let mdTitle = document.createElement('div');
+  const mdTitle = document.createElement('div');
   mdTitle.className = CLASSES.MD_TITLE;
-  let mdTitleTextwrap = document.createElement('span');
+  const mdTitleTextwrap = document.createElement('span');
   mdTitleTextwrap.innerText = data.title;
   mdTitle.style.direction = data.direction || 'ltr';
   mdTitleContainer.appendChild(mdTitle);
@@ -804,7 +804,7 @@
   mdTitle.appendChild(mdTitleTextwrap);
 
   if (!data.isAddButton) {
-    let mdMenu = document.createElement('button');
+    const mdMenu = document.createElement('button');
     mdMenu.className = CLASSES.MD_MENU;
     if (isCustomLinksEnabled) {
       mdMenu.classList.add(CLASSES.MD_EDIT_MENU);
@@ -852,7 +852,7 @@
 /**
  * Does some initialization and parses the query arguments passed to the iframe.
  */
-var init = function() {
+function init() {
   // Create a new DOM element to hold the tiles. The tiles will be added
   // one-by-one via addTile, and the whole thing will be inserted into the page
   // in swapInNewTiles, after the parent has sent us the 'show' message, and all
@@ -860,10 +860,10 @@
   tiles = document.createElement('div');
 
   // Parse query arguments.
-  var query = window.location.search.substring(1).split('&');
+  const query = window.location.search.substring(1).split('&');
   queryArgs = {};
-  for (var i = 0; i < query.length; ++i) {
-    var val = query[i].split('=');
+  for (let i = 0; i < query.length; ++i) {
+    const val = query[i].split('=');
     if (val[0] == '') {
       continue;
     }
@@ -874,7 +874,7 @@
 
   // Enable RTL.
   if (queryArgs['rtl'] == '1') {
-    var html = document.querySelector('html');
+    const html = document.querySelector('html');
     html.dir = 'rtl';
   }
 
@@ -901,7 +901,7 @@
   };
 
   window.addEventListener('message', handlePostMessage);
-};
+}
 
 
 window.addEventListener('DOMContentLoaded', init);
diff --git a/chrome/browser/resources/local_ntp/most_visited_util.js b/chrome/browser/resources/local_ntp/most_visited_util.js
index d568a79..13b583c 100644
--- a/chrome/browser/resources/local_ntp/most_visited_util.js
+++ b/chrome/browser/resources/local_ntp/most_visited_util.js
@@ -23,12 +23,12 @@
  * @return {Object} Dictionary containing name value pairs for URL.
  */
 function parseQueryParams(location) {
-  var params = Object.create(null);
-  var query = location.search.substring(1);
-  var vars = query.split('&');
-  for (var i = 0; i < vars.length; i++) {
-    var pair = vars[i].split('=');
-    var k = decodeURIComponent(pair[0]);
+  const params = Object.create(null);
+  const query = location.search.substring(1);
+  const vars = query.split('&');
+  for (let i = 0; i < vars.length; i++) {
+    const pair = vars[i].split('=');
+    const k = decodeURIComponent(pair[0]);
     if (k in params) {
       // Duplicate parameters are not allowed to prevent attackers who can
       // append things to |location| from getting their parameter values to
@@ -52,8 +52,8 @@
  * @return {!Element} A new link element.
  */
 function createMostVisitedLink(params, href, title, text, direction) {
-  var styles = getMostVisitedStyles(params, !!text);
-  var link = document.createElement('a');
+  const styles = getMostVisitedStyles(params, !!text);
+  const link = document.createElement('a');
   link.style.color = styles.color;
   link.style.fontSize = styles.fontSize + 'px';
   if (styles.fontFamily) {
@@ -63,9 +63,9 @@
     link.style.textAlign = styles.textAlign;
   }
   if (styles.textFadePos) {
-    var dir = /^rtl$/i.test(direction) ? 'to left' : 'to right';
+    const dir = /^rtl$/i.test(direction) ? 'to left' : 'to right';
     // The fading length in pixels is passed by the caller.
-    var mask = 'linear-gradient(' + dir + ', rgba(0,0,0,1), rgba(0,0,0,1) ' +
+    const mask = 'linear-gradient(' + dir + ', rgba(0,0,0,1), rgba(0,0,0,1) ' +
         styles.textFadePos + 'px, rgba(0,0,0,0))';
     link.style.textOverflow = 'clip';
     link.style.webkitMask = mask;
@@ -82,7 +82,7 @@
   link.tabIndex = '0';
   if (text) {
     // Wrap text with span so ellipsis will appear at the end of multiline.
-    var spanWrap = document.createElement('span');
+    const spanWrap = document.createElement('span');
     spanWrap.textContent = text;
     link.appendChild(spanWrap);
   }
@@ -123,7 +123,7 @@
   // 'RRGGBBAA' color format overrides everything.
   if ('c' in params && params.c.match(/^[0-9A-Fa-f]{8}$/)) {
     // Extract the 4 pairs of hex digits, map to number, then form rgba().
-    var t = params.c.match(/(..)(..)(..)(..)/).slice(1).map(function(s) {
+    const t = params.c.match(/(..)(..)(..)(..)/).slice(1).map(function(s) {
       return parseInt(s, 16);
     });
     return 'rgba(' + t[0] + ',' + t[1] + ',' + t[2] + ',' + t[3] / 255 + ')';
@@ -131,9 +131,9 @@
 
   // For backward compatibility with server-side NTP, look at themes directly
   // and use param.c for non-title or as fallback.
-  var apiHandle = chrome.embeddedSearch.newTabPage;
-  var themeInfo = apiHandle.themeBackgroundInfo;
-  var c = '#777';
+  const apiHandle = chrome.embeddedSearch.newTabPage;
+  const themeInfo = apiHandle.themeBackgroundInfo;
+  let c = '#777';
   if (isTitle && themeInfo && !themeInfo.usingDefaultTheme) {
     // Read from theme directly
     c = convertArrayToRGBAColor(themeInfo.textColorRgba) || c;
@@ -157,7 +157,7 @@
  * @return {Object} Styles suitable for CSS interpolation.
  */
 function getMostVisitedStyles(params, isTitle) {
-  var styles = {
+  const styles = {
     color: getTextColor(params, isTitle),  // Handles 'c' in params.
     fontFamily: '',
     fontSize: 11
@@ -172,13 +172,13 @@
     styles.textAlign = params.ta;
   }
   if ('tf' in params) {
-    var tf = parseInt(params.tf, 10);
+    const tf = parseInt(params.tf, 10);
     if (isFinite(tf)) {
       styles.textFadePos = tf;
     }
   }
   if ('ntl' in params) {
-    var ntl = parseInt(params.ntl, 10);
+    const ntl = parseInt(params.ntl, 10);
     if (isFinite(ntl)) {
       styles.numTitleLines = ntl;
     }
@@ -191,7 +191,7 @@
  * Returns whether the given URL has a known, safe scheme.
  * @param {string} url URL to check.
  */
-var isSchemeAllowed = function(url) {
+const isSchemeAllowed = function(url) {
   return url.startsWith('http://') || url.startsWith('https://') ||
       url.startsWith('ftp://') || url.startsWith('chrome-extension://');
 };
@@ -203,12 +203,12 @@
  *     data to fill.
  */
 function fillMostVisited(location, fill) {
-  var params = parseQueryParams(location);
+  const params = parseQueryParams(location);
   params.rid = parseInt(params.rid, 10);
   if (!isFinite(params.rid)) {
     return;
   }
-  var data =
+  const data =
       chrome.embeddedSearch.newTabPage.getMostVisitedItemData(params.rid);
   if (!data) {
     return;
diff --git a/chrome/browser/resources/local_ntp/utils.js b/chrome/browser/resources/local_ntp/utils.js
index dd9dc02..5b6f6b3 100644
--- a/chrome/browser/resources/local_ntp/utils.js
+++ b/chrome/browser/resources/local_ntp/utils.js
@@ -28,7 +28,7 @@
 /**
  * Contains common functions used in the main NTP page and its iframes.
  */
-let utils = {};
+const utils = {};
 
 
 /**
diff --git a/chrome/browser/resources/local_ntp/voice.js b/chrome/browser/resources/local_ntp/voice.js
index 060972c..bb16fc8 100644
--- a/chrome/browser/resources/local_ntp/voice.js
+++ b/chrome/browser/resources/local_ntp/voice.js
@@ -100,7 +100,7 @@
  * href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">
  * Web Speech API</a>, error handling and executing search queries.
  */
-let speech = {};
+const speech = {};
 
 
 /**
@@ -976,7 +976,7 @@
  * Provides methods for styling and animating the text areas
  * left of the microphone button.
  */
-let text = {};
+const text = {};
 
 
 /**
@@ -1200,7 +1200,7 @@
  * @private
  */
 text.getErrorLink_ = function(error) {
-  let linkElement = document.createElement('a');
+  const linkElement = document.createElement('a');
   linkElement.className = text.ERROR_LINK_CLASS_;
 
   switch (error) {
@@ -1306,7 +1306,7 @@
  * Provides methods for animating the microphone button and icon
  * on the Voice Search full screen overlay.
  */
-let microphone = {};
+const microphone = {};
 
 
 /**
@@ -1439,7 +1439,7 @@
  * Provides methods for manipulating and animating the Voice Search
  * full screen overlay.
  */
-let view = {};
+const view = {};
 
 
 /**
@@ -1639,7 +1639,7 @@
  * @param {!Object} translatedStrings Dictionary of localized title strings.
  */
 view.setTitles = function(translatedStrings) {
-  let closeButton = $(view.CLOSE_BUTTON_ID);
+  const closeButton = $(view.CLOSE_BUTTON_ID);
   closeButton.title = translatedStrings.voiceCloseTooltip;
   closeButton.setAttribute('aria-label', translatedStrings.voiceCloseTooltip);
 };
diff --git a/chrome/browser/resources/print_preview/print_preview.html b/chrome/browser/resources/print_preview/print_preview.html
index 4940d59..f4e9589 100644
--- a/chrome/browser/resources/print_preview/print_preview.html
+++ b/chrome/browser/resources/print_preview/print_preview.html
@@ -3,6 +3,9 @@
     $i18n{dark} $i18n{newprintpreviewlayout}>
 <head>
   <meta charset="utf-8">
+<if expr="not optimize_webui">
+  <base href="chrome://print">
+</if>
   <style>
     html {
       background: rgb(232, 234, 237);  /* --google-grey-200 */
diff --git a/chrome/browser/resources/print_preview/print_preview_resources.grd b/chrome/browser/resources/print_preview/print_preview_resources.grd
index fe09b546..ae1e4dcb 100644
--- a/chrome/browser/resources/print_preview/print_preview_resources.grd
+++ b/chrome/browser/resources/print_preview/print_preview_resources.grd
@@ -14,7 +14,8 @@
     <structures>
       <structure name="IDR_PRINT_PREVIEW_HTML"
                  file="print_preview.html"
-                 type="chrome_html" />
+                 type="chrome_html"
+                 preprocess="true" />
       <structure name="IDR_PRINT_PREVIEW_UI_APP_HTML"
                  file="ui/app.html"
                  type="chrome_html"
diff --git a/chrome/browser/resources/print_preview/ui/app.js b/chrome/browser/resources/print_preview/ui/app.js
index 267ba46..6758986 100644
--- a/chrome/browser/resources/print_preview/ui/app.js
+++ b/chrome/browser/resources/print_preview/ui/app.js
@@ -108,6 +108,17 @@
   openDialogs_: [],
 
   /** @override */
+  created: function() {
+    // Regular expression that captures the leading slash, the content and the
+    // trailing slash in three different groups.
+    const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
+    const path = location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2');
+    if (path !== '/') {  // There are no subpages in Print Preview.
+      window.history.replaceState(undefined /* stateObject */, '', '/');
+    }
+  },
+
+  /** @override */
   ready: function() {
     cr.ui.FocusOutlineManager.forDocument(document);
   },
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
index c86857a7..1f20da28 100644
--- a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
+++ b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.html
@@ -170,10 +170,22 @@
 <dom-module id="add-printer-manufacturer-model-dialog">
   <template>
     <style include="cups-printer-shared">
+      .subtext {
+        margin-bottom: 10px;
+        padding-inline-start: 20px;
+      }
     </style>
     <add-printer-dialog>
-      <div slot="dialog-title">$i18n{selectManufacturerAndModelTitle}</div>
+      <div slot="dialog-title">$i18n{manufacturerAndModelDialogTitle}</div>
       <div slot="dialog-body">
+        <div class="subtext" id="makeModelTextInfo"
+            hidden="[[!activePrinter.printerMakeAndModel]]">
+          <span>[[getManufacturerAndModelSubtext_(
+              activePrinter.printerMakeAndModel)]]</span>
+          <a href="$i18n{printingCUPSPrintLearnMoreUrl}" target="_blank">
+            $i18n{learnMore}
+          </a>
+        </div>
         <div class="settings-box two-line">
           <cr-searchable-drop-down items="[[manufacturerList]]"
               label="$i18n{printerManufacturer}"
diff --git a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
index 0b92efa..80ca448 100644
--- a/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
+++ b/chrome/browser/resources/settings/printing_page/cups_add_printer_dialog.js
@@ -253,6 +253,17 @@
   },
 
   /**
+   * @param {string} printerMakeAndModel
+   * @return {string} The additional information subtext of the manufacturer and
+   * model dialog.
+   * @private
+   */
+  getManufacturerAndModelSubtext_: function(printerMakeAndModel) {
+    return loadTimeData.getStringF(
+        'manufacturerAndModelAdditionalInformation', printerMakeAndModel);
+  },
+
+  /**
    * @param {string} manufacturer The manufacturer for which we are retrieving
    *     models.
    * @private
diff --git a/chrome/browser/resources/snippets_internals/snippets_internals.html b/chrome/browser/resources/snippets_internals/snippets_internals.html
index e004b60..84700c6 100644
--- a/chrome/browser/resources/snippets_internals/snippets_internals.html
+++ b/chrome/browser/resources/snippets_internals/snippets_internals.html
@@ -123,14 +123,6 @@
     </div>
   </div>
 
-  <div id="notifications">
-    <h2>Notifications</h2>
-    <button id="reset-notifications-state-button">
-      Reset notifications state
-    </button>
-  </div>
-
-
   <div id="content-suggestions">
     <h2>Content suggestions by category
       <span class="detail">(click for details)</span>
diff --git a/chrome/browser/resources/snippets_internals/snippets_internals.js b/chrome/browser/resources/snippets_internals/snippets_internals.js
index 27b09c3..ef8c6e8 100644
--- a/chrome/browser/resources/snippets_internals/snippets_internals.js
+++ b/chrome/browser/resources/snippets_internals/snippets_internals.js
@@ -243,16 +243,6 @@
     });
   });
 
-  $('reset-notifications-state-button')
-      .addEventListener('click', function(event) {
-        pageHandler.resetNotificationState();
-      });
-
-  $('reset-notifications-state-button')
-      .addEventListener('click', function(event) {
-        pageHandler.resetNotificationState();
-      });
-
   $('submit-dump').addEventListener('click', function(event) {
     pageHandler.getSuggestionsByCategory().then(function(response) {
       downloadJson('snippets.json', JSON.stringify(response.categories));
diff --git a/chrome/browser/safe_browsing/threat_details_unittest.cc b/chrome/browser/safe_browsing/threat_details_unittest.cc
index 4b21c607..ab3be487 100644
--- a/chrome/browser/safe_browsing/threat_details_unittest.cc
+++ b/chrome/browser/safe_browsing/threat_details_unittest.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "base/bind.h"
+#include "base/hash/md5.h"
 #include "base/macros.h"
 #include "base/pickle.h"
 #include "base/run_loop.h"
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
index b07996e..573bf11 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.cc
@@ -148,6 +148,11 @@
   activated_via_devtools_ = force_activation;
 }
 
+const subresource_filter::ContentSubresourceFilterThrottleManager*
+ChromeSubresourceFilterClient::GetThrottleManager() const {
+  return throttle_manager_.get();
+}
+
 // static
 void ChromeSubresourceFilterClient::LogAction(SubresourceFilterAction action) {
   UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.Actions2", action);
diff --git a/chrome/browser/subresource_filter/chrome_subresource_filter_client.h b/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
index e9def9a9..e36bc93c 100644
--- a/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
+++ b/chrome/browser/subresource_filter/chrome_subresource_filter_client.h
@@ -90,6 +90,9 @@
     return did_show_ui_for_navigation_;
   }
 
+  const subresource_filter::ContentSubresourceFilterThrottleManager*
+  GetThrottleManager() const;
+
   static void LogAction(SubresourceFilterAction action);
 
  private:
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index e3e52c3..d2b886e 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -779,6 +779,8 @@
   SkColor tab_color;
   pack->GetColor(TP::COLOR_TOOLBAR, &tab_color);
   pack->SetColor(TP::COLOR_NTP_BACKGROUND, tab_color);
+  pack->SetColor(TP::COLOR_NTP_TEXT,
+                 color_utils::GetColorWithMaxContrast(tab_color));
 
   SkColor tab_text_color;
   pack->GetColor(TP::COLOR_TAB_TEXT, &tab_text_color);
@@ -1079,6 +1081,9 @@
   // with the frame/tab behind them.
   GenerateMissingTextColors();
 
+  // Generates missing NTP related colors.
+  GenerateMissingNtpColors();
+
   // Make sure the |images_on_file_thread_| has bitmaps for supported
   // scale factors before passing to FILE thread.
   images_on_file_thread_ = images_;
@@ -1830,6 +1835,22 @@
   SetColor(text_color_id, result_color);
 }
 
+void BrowserThemePack::GenerateMissingNtpColors() {
+  // Calculate NTP text color based on NTP background.
+  SkColor ntp_background_color;
+  gfx::Image image = GetImageNamed(IDR_THEME_NTP_BACKGROUND);
+  if (!image.IsEmpty()) {
+    ntp_background_color = ComputeImageColor(image, image.Height());
+    SetColorIfUnspecified(
+        TP::COLOR_NTP_TEXT,
+        color_utils::GetColorWithMaxContrast(ntp_background_color));
+  } else if (GetColor(TP::COLOR_NTP_BACKGROUND, &ntp_background_color)) {
+    SetColorIfUnspecified(
+        TP::COLOR_NTP_TEXT,
+        color_utils::GetColorWithMaxContrast(ntp_background_color));
+  }
+}
+
 void BrowserThemePack::RepackImages(const ImageCache& images,
                                     RawImages* reencoded_images) const {
   for (auto it = images.begin(); it != images.end(); ++it) {
diff --git a/chrome/browser/themes/browser_theme_pack.h b/chrome/browser/themes/browser_theme_pack.h
index 1476a48..a6aed68 100644
--- a/chrome/browser/themes/browser_theme_pack.h
+++ b/chrome/browser/themes/browser_theme_pack.h
@@ -240,6 +240,9 @@
                                      int frame_color_id,
                                      int source_color_id);
 
+  // Generates missing NTP related colors.
+  void GenerateMissingNtpColors();
+
   // Takes all the SkBitmaps in |images|, encodes them as PNGs and places
   // them in |reencoded_images|.
   void RepackImages(const ImageCache& images,
diff --git a/chrome/browser/themes/browser_theme_pack_unittest.cc b/chrome/browser/themes/browser_theme_pack_unittest.cc
index a5ab8edd..6a749e8 100644
--- a/chrome/browser/themes/browser_theme_pack_unittest.cc
+++ b/chrome/browser/themes/browser_theme_pack_unittest.cc
@@ -1217,3 +1217,23 @@
   color_utils::HSL hsl;
   EXPECT_TRUE(pack->GetTint(TP::TINT_BUTTONS, &hsl));
 }
+
+TEST_F(BrowserThemePackTest, TestNtpTextCaclulation) {
+  // If ntp_text is not specified then it should be calculated based on NTP
+  // background image.
+  scoped_refptr<BrowserThemePack> pack(
+      new BrowserThemePack(CustomThemeSupplier::ThemeType::EXTENSION));
+  BuildTestExtensionTheme("theme_ntp_background_image", pack.get());
+
+  SkColor ntp_text;
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_NTP_TEXT, &ntp_text));
+
+  // If ntp_text is not specified then it should be calculated based on NTP
+  // background color.
+  scoped_refptr<BrowserThemePack> pack_autogenerated(
+      new BrowserThemePack(CustomThemeSupplier::ThemeType::AUTOGENERATED));
+  BrowserThemePack::BuildFromColor(SkColorSetRGB(0, 130, 130),
+                                   pack_autogenerated.get());
+
+  EXPECT_TRUE(pack_autogenerated->GetColor(TP::COLOR_NTP_TEXT, &ntp_text));
+}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index d97713c..ba4d31a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1823,6 +1823,7 @@
       "//chromeos/services/assistant/public:feature_flags",
       "//chromeos/services/assistant/public/mojom",
       "//chromeos/services/assistant/public/proto:proto",
+      "//chromeos/services/cellular_setup/public/mojom",
       "//chromeos/services/multidevice_setup/public/cpp",
       "//chromeos/services/multidevice_setup/public/cpp:android_sms_app_helper_delegate",
       "//chromeos/services/multidevice_setup/public/cpp:prefs",
@@ -2184,11 +2185,11 @@
 
     deps += [
       "//chrome/browser/apps/app_shim",
+      "//components/remote_cocoa/app_shim",
       "//extensions/components/native_app_window",
       "//third_party/google_toolbox_for_mac",
       "//third_party/mozilla",
       "//ui/accelerated_widget_mac:accelerated_widget_mac",
-      "//ui/views_bridge_mac",
     ]
     include_dirs = [ "$target_gen_dir" ]
     libs += [
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index 23fa75e..96f7622c 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -67,7 +67,6 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h"
 #include "chrome/browser/ui/ash/session_controller_client.h"
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
@@ -396,16 +395,6 @@
                          ash::LAUNCH_FROM_UNKNOWN, base::DoNothing());
 }
 
-// Flushes the binding related classes (including the window service) used by
-// this code to ensure state propagations have completed.
-void FlushBindings() {
-  // Flush all window-service related changed.
-  aura::test::WaitForAllChangesToComplete();
-
-  // And flush the MultiUserWindowManagerClient pipe.
-  MultiUserWindowManagerClientImplTestHelper::FlushBindings();
-}
-
 }  // namespace
 
 // A test ChromeLauncherController subclass that uses TestShelfController.
@@ -648,7 +637,6 @@
   // Create and initialize the controller, owned by the test shell delegate.
   void InitLauncherController() {
     CreateLauncherController()->Init();
-    FlushBindings();
   }
 
   // Create and initialize the controller; create a tab and show the browser.
@@ -1199,9 +1187,6 @@
     window_->Init(GURL(std::string()),
                   new extensions::AppWindowContentsImpl(window_),
                   creator_web_contents_->GetMainFrame(), params);
-    // The visibility may be controlled by ash. Call FlushBindings() to ensure
-    // ash has completed processing the newly added window.
-    FlushBindings();
   }
 
   virtual ~V2App() {
@@ -1296,10 +1281,6 @@
         ash::MultiUserWindowManagerImpl::ANIMATION_SPEED_DISABLED);
     ash::MultiUserWindowManagerImpl::Get()->OnActiveUserSessionChanged(
         account_id);
-    // Call FlushBindings() to ensure ash has completed processing of the
-    // switch.
-    FlushBindings();
-
     // TODO(crbug.com/956841) This should be redundant with the FlushBindings
     // call, but removing it breaks some tests.
     launcher_controller_->browser_status_monitor_for_test()->ActiveUserChanged(
@@ -3489,9 +3470,6 @@
 
   // Teleport the app from user #1 to the desktop #2 should show it.
   client->ShowWindowForUser(v2_app_1.window()->GetNativeWindow(), account_id2);
-  // FlushBindings() to ensure ash has completed processing, which results in
-  // changing visibility of windows.
-  FlushBindings();
   EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
   EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
 
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h
index 2827e450..2fae48b 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h
@@ -27,11 +27,8 @@
 //
 // See ash::MultiUserWindowManager for more details on the API provided here.
 //
-// WARNING: in mash this code ends up referencing windows with an aura Env
-// of MUS *and* windows with an aura Env of LOCAL. This is because Arc/Crostini
-// windows are created by Ash, and browser/app windows are created by the
-// browser. Long term this code needs to be refactored out of chrome, at which
-// time *all* windows should be created by Ash.
+// TODO(sky): this name is confusing. Maybe it should be
+// ChromeMultiUserWindowManager.
 class MultiUserWindowManagerClient {
  public:
   // Observer to notify of any window owner changes.
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.cc b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.cc
index 18848c1..29658149 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.cc
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.cc
@@ -7,7 +7,7 @@
 #include <set>
 #include <vector>
 
-#include "ash/multi_user/multi_user_window_manager_impl.h"
+#include "ash/public/cpp/multi_user_window_manager.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
 #include "chrome/browser/browser_process.h"
@@ -26,14 +26,8 @@
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "ui/aura/client/aura_constants.h"
-#include "ui/aura/env.h"
-#include "ui/aura/mus/window_mus.h"
-#include "ui/aura/mus/window_tree_client.h"
 #include "ui/aura/window.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/base/ui_base_types.h"
-#include "ui/views/mus/mus_client.h"
-#include "ui/views/widget/widget.h"
 
 namespace {
 
@@ -86,26 +80,6 @@
                             NUM_TELEPORT_WINDOW_TYPES);
 }
 
-// Returns the WindowMus to use when sending messages to the server.
-aura::WindowMus* GetWindowMus(aura::Window* window) {
-  if (!aura::WindowMus::Get(window))
-    return nullptr;
-
-  aura::Window* root_window = window->GetRootWindow();
-  if (!root_window)
-    return nullptr;
-
-  // DesktopNativeWidgetAura creates two aura Windows. GetNativeWindow() returns
-  // the child window. Get the widget for |window| and its root. If the Widgets
-  // are the same, it means |window| is the native window of a
-  // DesktopNativeWidgetAura. Use the root window to notify the server as that
-  // corresponds to the top-level window that ash knows about.
-  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
-  views::Widget* root_widget =
-      views::Widget::GetWidgetForNativeView(root_window);
-  return widget == root_widget ? aura::WindowMus::Get(root_window) : nullptr;
-}
-
 }  // namespace
 
 // This class keeps track of all applications which were started for a user.
@@ -135,39 +109,12 @@
 MultiUserWindowManagerClientImpl* MultiUserWindowManagerClientImpl::instance_ =
     nullptr;
 
-MultiUserWindowManagerClientImpl::ClassicSupport::ClassicSupport(
-    MultiUserWindowManagerClientImpl* host)
-    : binding(host) {}
-
-MultiUserWindowManagerClientImpl::ClassicSupport::~ClassicSupport() = default;
-
 MultiUserWindowManagerClientImpl::MultiUserWindowManagerClientImpl(
-    const AccountId& current_account_id)
-    : current_account_id_(current_account_id) {
+    const AccountId& current_account_id) {
   DCHECK(!instance_);
   instance_ = this;
-  ash::mojom::MultiUserWindowManagerClient* client = nullptr;
-  if (features::IsUsingWindowService()) {
-    // This path doesn't set |client| as it'll be wired up in ash when it
-    // sees the MultiUserWindowManagerClient registration.
-    multi_user_window_manager_mojom_ =
-        views::MusClient::Get()
-            ->window_tree_client()
-            ->BindWindowManagerInterface<ash::mojom::MultiUserWindowManager>();
-    ash::mojom::MultiUserWindowManagerClientAssociatedPtrInfo ptr_info;
-    client_binding_.Bind(mojo::MakeRequest(&ptr_info));
-    multi_user_window_manager_mojom_->SetClient(std::move(ptr_info));
-  } else {
-    classic_support_ = std::make_unique<ClassicSupport>(this);
-    classic_support_->binding.Bind(
-        mojo::MakeRequest(&classic_support_->client_ptr));
-    client = classic_support_->client_ptr.get();
-  }
-  if (!features::IsMultiProcessMash()) {
-    ash_multi_user_window_manager_ =
-        std::make_unique<ash::MultiUserWindowManagerImpl>(client, this,
-                                                          current_account_id);
-  }
+  ash_multi_user_window_manager_ =
+      ash::MultiUserWindowManager::Create(this, current_account_id);
 }
 
 MultiUserWindowManagerClientImpl::~MultiUserWindowManagerClientImpl() {
@@ -177,15 +124,6 @@
   // This may trigger callbacks to us, delete it early on.
   ash_multi_user_window_manager_.reset();
 
-  // Remove all window observers.
-  while (!window_to_entry_.empty()) {
-    // Explicitly remove this from window observer list since OnWindowDestroyed
-    // no longer does that.
-    aura::Window* window = window_to_entry_.begin()->first;
-    window->RemoveObserver(this);
-    OnWindowDestroyed(window);
-  }
-
   // Remove all app observers.
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   // might be nullptr in unit tests.
@@ -209,7 +147,8 @@
 void MultiUserWindowManagerClientImpl::Init() {
   // Since we are setting the SessionStateObserver and adding the user, this
   // function should get called only once.
-  DCHECK(account_id_to_app_observer_.find(current_account_id_) ==
+  auto current_account_id = ash_multi_user_window_manager_->CurrentAccountId();
+  DCHECK(account_id_to_app_observer_.find(current_account_id) ==
          account_id_to_app_observer_.end());
 
   // The BrowserListObserver would have been better to use then the old
@@ -219,7 +158,7 @@
 
   // Add an app window observer & all already running apps.
   Profile* profile =
-      multi_user_util::GetProfileFromAccountId(current_account_id_);
+      multi_user_util::GetProfileFromAccountId(current_account_id);
   if (profile)
     AddUser(profile);
 }
@@ -233,35 +172,13 @@
   if (GetWindowOwner(window) == account_id)
     return;
 
-  DCHECK(GetWindowOwner(window).empty());
-  std::unique_ptr<WindowEntry> window_entry_ptr =
-      std::make_unique<WindowEntry>(account_id);
-  WindowEntry* window_entry = window_entry_ptr.get();
-  window_to_entry_[window] = std::move(window_entry_ptr);
-
   // Check if this window was created due to a user interaction. If it was,
   // transfer it to the current user.
+  // TODO: get rid of aura::client::kCreatedByUserGesture.
   const bool show_for_current_user =
       window->GetProperty(aura::client::kCreatedByUserGesture);
-  if (window->env()->mode() == aura::Env::Mode::MUS) {
-    aura::WindowMus* window_mus = GetWindowMus(window);
-    if (window_mus) {
-      multi_user_window_manager_mojom_->SetWindowOwner(
-          window_mus->server_id(), account_id, show_for_current_user);
-    }  // else case can happen during shutdown, or for child windows.
-  } else {
-    // If there is a non-MUS window, then we must have created
-    // |ash_multi_user_window_manager_|.
-    DCHECK(ash_multi_user_window_manager_);
-    ash_multi_user_window_manager_->SetWindowOwner(window, account_id,
-                                                   show_for_current_user);
-  }
-
-  // Add observers to track state changes.
-  window->AddObserver(this);
-
-  if (show_for_current_user)
-    window_entry->set_show_for_user(current_account_id_);
+  ash_multi_user_window_manager_->SetWindowOwner(window, account_id,
+                                                 show_for_current_user);
 
   // Notify entry adding.
   for (Observer& observer : observers_)
@@ -270,12 +187,7 @@
 
 const AccountId& MultiUserWindowManagerClientImpl::GetWindowOwner(
     const aura::Window* window) const {
-  for (WindowToEntryMap::const_iterator it = window_to_entry_.begin();
-       it != window_to_entry_.end(); it++) {
-    if (it->first == window)
-      return it->second->owner();
-  }
-  return EmptyAccountId();
+  return ash_multi_user_window_manager_->GetWindowOwner(window);
 }
 
 void MultiUserWindowManagerClientImpl::ShowWindowForUser(
@@ -284,31 +196,16 @@
   if (!window)
     return;
 
-  if (window->env()->mode() == aura::Env::Mode::MUS) {
-    aura::WindowMus* window_mus = GetWindowMus(window);
-    if (window_mus) {
-      multi_user_window_manager_mojom_->ShowWindowForUser(
-          window_mus->server_id(), account_id);
-    }
-  } else {
-    ash_multi_user_window_manager_->ShowWindowForUser(window, account_id);
-  }
+  ash_multi_user_window_manager_->ShowWindowForUser(window, account_id);
 }
 
 bool MultiUserWindowManagerClientImpl::AreWindowsSharedAmongUsers() const {
-  for (auto& window_pair : window_to_entry_) {
-    if (window_pair.second->owner() != window_pair.second->show_for_user())
-      return true;
-  }
-  return false;
+  return ash_multi_user_window_manager_->AreWindowsSharedAmongUsers();
 }
 
 void MultiUserWindowManagerClientImpl::GetOwnersOfVisibleWindows(
     std::set<AccountId>* account_ids) const {
-  for (auto& window_pair : window_to_entry_) {
-    if (window_pair.first->IsVisible())
-      account_ids->insert(window_pair.second->owner());
-  }
+  *account_ids = ash_multi_user_window_manager_->GetOwnersOfVisibleWindows();
 }
 
 bool MultiUserWindowManagerClientImpl::IsWindowOnDesktopOfUser(
@@ -320,15 +217,7 @@
 
 const AccountId& MultiUserWindowManagerClientImpl::GetUserPresentingWindow(
     const aura::Window* window) const {
-  for (WindowToEntryMap::const_iterator it = window_to_entry_.begin();
-       it != window_to_entry_.end(); it++) {
-    if (it->first == window)
-      // We ask the object for its desktop.
-      return it->second->show_for_user();
-  }
-  // If the window is not owned by anyone it is shown on all desktops and we
-  // return the empty string.
-  return EmptyAccountId();
+  return ash_multi_user_window_manager_->GetUserPresentingWindow(window);
 }
 
 void MultiUserWindowManagerClientImpl::AddUser(
@@ -370,10 +259,6 @@
   observers_.RemoveObserver(observer);
 }
 
-void MultiUserWindowManagerClientImpl::OnWindowDestroyed(aura::Window* window) {
-  window_to_entry_.erase(window);
-}
-
 void MultiUserWindowManagerClientImpl::Observe(
     int type,
     const content::NotificationSource& source,
@@ -382,20 +267,14 @@
   AddBrowserWindow(content::Source<Browser>(source).ptr());
 }
 
-void MultiUserWindowManagerClientImpl::OnOwnerEntryChanged(
+void MultiUserWindowManagerClientImpl::OnWindowOwnerEntryChanged(
     aura::Window* window,
     const AccountId& account_id,
     bool was_minimized,
     bool teleported) {
-  WindowToEntryMap::iterator it = window_to_entry_.find(window);
-  if (it == window_to_entry_.end())
-    return;
-
   if (was_minimized)
     RecordUMAForTransferredWindowType(window);
 
-  it->second->set_show_for_user(account_id);
-
   const AccountId& owner = GetWindowOwner(window);
   // Browser windows don't use kAvatarIconKey. See
   // BrowserNonClientFrameViewAsh::UpdateProfileIcons().
@@ -404,14 +283,12 @@
         user_manager::UserManager::IsInitialized()
             ? user_manager::UserManager::Get()->FindUser(owner)
             : nullptr;
-    aura::Window* property_window =
-        features::IsUsingWindowService() ? window->GetRootWindow() : window;
     if (window_owner && teleported) {
-      property_window->SetProperty(
+      window->SetProperty(
           aura::client::kAvatarIconKey,
           new gfx::ImageSkia(GetAvatarImageForUser(window_owner)));
     } else {
-      property_window->ClearProperty(aura::client::kAvatarIconKey);
+      window->ClearProperty(aura::client::kAvatarIconKey);
     }
   }
 
@@ -419,17 +296,14 @@
     observer.OnOwnerEntryChanged(window);
 }
 
-void MultiUserWindowManagerClientImpl::OnWillSwitchActiveAccount(
-    const AccountId& account_id) {
-  current_account_id_ = account_id;
-}
-
 void MultiUserWindowManagerClientImpl::OnTransitionUserShelfToNewAccount() {
   ChromeLauncherController* chrome_launcher_controller =
       ChromeLauncherController::instance();
   // Some unit tests have no ChromeLauncherController.
-  if (chrome_launcher_controller)
-    chrome_launcher_controller->ActiveUserChanged(current_account_id_);
+  if (!chrome_launcher_controller)
+    return;
+  chrome_launcher_controller->ActiveUserChanged(
+      ash_multi_user_window_manager_->CurrentAccountId());
 }
 
 void MultiUserWindowManagerClientImpl::OnDidSwitchActiveAccount() {
@@ -439,7 +313,7 @@
 
 const AccountId& MultiUserWindowManagerClientImpl::GetCurrentUserForTest()
     const {
-  return current_account_id_;
+  return ash_multi_user_window_manager_->CurrentAccountId();
 }
 
 void MultiUserWindowManagerClientImpl::AddBrowserWindow(Browser* browser) {
@@ -450,24 +324,3 @@
   SetWindowOwner(browser->window()->GetNativeWindow(),
                  multi_user_util::GetAccountIdFromProfile(browser->profile()));
 }
-
-void MultiUserWindowManagerClientImpl::OnWindowOwnerEntryChanged(
-    ws::Id window_id,
-    const AccountId& account_id,
-    bool was_minimized,
-    bool teleported) {
-  // This undoes the logic in GetWindowMus(). See it for details.
-  aura::WindowMus* window_mus =
-      views::MusClient::Get()->window_tree_client()->GetWindowByServerId(
-          window_id);
-  if (!window_mus)
-    return;
-
-  views::Widget* widget =
-      views::Widget::GetWidgetForNativeWindow(window_mus->GetWindow());
-  if (!widget)
-    return;
-
-  OnOwnerEntryChanged(widget->GetNativeWindow(), account_id, was_minimized,
-                      teleported);
-}
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h
index 75fdc90..cb6d7b2 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h
@@ -8,24 +8,19 @@
 #include <map>
 #include <memory>
 
-#include "ash/multi_user/multi_user_window_manager_delegate_classic.h"
-#include "ash/public/interfaces/multi_user_window_manager.mojom.h"
+#include "ash/public/cpp/multi_user_window_manager_delegate.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h"
 #include "components/account_id/account_id.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "services/ws/common/types.h"
-#include "ui/aura/window_observer.h"
 
 class AppObserver;
 class Browser;
 
 namespace ash {
-class MultiUserWindowManagerImpl;
+class MultiUserWindowManager;
 class MultiUserWindowManagerClientImplTest;
 }  // namespace ash
 
@@ -40,9 +35,7 @@
 // Real implementation of MultiUserWindowManagerClient, see it for details.
 class MultiUserWindowManagerClientImpl
     : public MultiUserWindowManagerClient,
-      public ash::MultiUserWindowManagerDelegateClassic,
-      public ash::mojom::MultiUserWindowManagerClient,
-      public aura::WindowObserver,
+      public ash::MultiUserWindowManagerDelegate,
       public content::NotificationObserver {
  public:
   // Create the manager and use |active_account_id| as the active user.
@@ -52,7 +45,7 @@
   // Initializes the manager after its creation. Should only be called once.
   void Init();
 
-  // MultiUserWindowManager overrides:
+  // MultiUserWindowManagerClient overrides:
   void SetWindowOwner(aura::Window* window,
                       const AccountId& account_id) override;
   const AccountId& GetWindowOwner(const aura::Window* window) const override;
@@ -69,20 +62,11 @@
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
 
-  // WindowObserver overrides:
-  void OnWindowDestroyed(aura::Window* window) override;
-
   // content::NotificationObserver overrides:
   void Observe(int type,
                const content::NotificationSource& source,
                const content::NotificationDetails& details) override;
 
-  // ash::MultiUserWindowManagerDelegateClassic overrides:
-  void OnOwnerEntryChanged(aura::Window* window,
-                           const AccountId& account_id,
-                           bool was_minimized,
-                           bool teleported) override;
-
   // Returns the current user for unit tests.
   const AccountId& GetCurrentUserForTest() const;
 
@@ -90,51 +74,11 @@
   friend class ash::MultiUserWindowManagerClientImplTest;
   friend class MultiUserWindowManagerClientImplTestHelper;
 
-  // Used only in classic mode. In classic mode a mojo Binding is used that
-  // results in the delegate being notified async. Doing this gives the same
-  // async delay seen when the WindowService is used.
-  struct ClassicSupport {
-    explicit ClassicSupport(MultiUserWindowManagerClientImpl* host);
-    ~ClassicSupport();
-
-    ash::mojom::MultiUserWindowManagerClientPtr client_ptr;
-    mojo::Binding<ash::mojom::MultiUserWindowManagerClient> binding;
-  };
-
-  class WindowEntry {
-   public:
-    explicit WindowEntry(const AccountId& account_id)
-        : owner_(account_id), show_for_user_(account_id) {}
-    ~WindowEntry() {}
-
-    // Returns the owner of this window. This cannot be changed.
-    const AccountId& owner() const { return owner_; }
-
-    // Returns the user for which this should be shown.
-    const AccountId& show_for_user() const { return show_for_user_; }
-
-    // Set the user which will display the window on the owned desktop. If
-    // an empty user id gets passed the owner will be used.
-    void set_show_for_user(const AccountId& account_id) {
-      show_for_user_ = account_id.is_valid() ? account_id : owner_;
-    }
-
-   private:
-    // The user id of the owner of this window.
-    const AccountId owner_;
-
-    // The user id of the user on which desktop the window gets shown.
-    AccountId show_for_user_;
-
-    DISALLOW_COPY_AND_ASSIGN(WindowEntry);
-  };
-
-  // ash::mojom::MultiUserWindowManagerClient:
-  void OnWindowOwnerEntryChanged(ws::Id window_id,
+  // ash::MultiUserWindowManagerDelegate:
+  void OnWindowOwnerEntryChanged(aura::Window* window,
                                  const AccountId& account_id,
                                  bool was_minimized,
                                  bool teleported) override;
-  void OnWillSwitchActiveAccount(const AccountId& account_id) override;
   void OnTransitionUserShelfToNewAccount() override;
   void OnDidSwitchActiveAccount() override;
 
@@ -145,15 +89,6 @@
   // tests.
   static MultiUserWindowManagerClientImpl* instance_;
 
-  std::unique_ptr<ClassicSupport> classic_support_;
-
-  using WindowToEntryMap =
-      std::map<aura::Window*, std::unique_ptr<WindowEntry>>;
-
-  // A lookup to see to which user the given window belongs to, where and if it
-  // should get shown.
-  WindowToEntryMap window_to_entry_;
-
   using AccountIdToAppWindowObserver = std::map<AccountId, AppObserver*>;
 
   // A list of all known users and their app window observers.
@@ -162,26 +97,10 @@
   // An observer list to be notified upon window owner changes.
   base::ObserverList<Observer>::Unchecked observers_;
 
-  // The currently selected active user. It is used to find the proper
-  // visibility state in various cases. The state is stored here instead of
-  // being read from the user manager to be in sync while a switch occurs.
-  AccountId current_account_id_;
-
   // The notification registrar to track the creation of browser windows.
   content::NotificationRegistrar registrar_;
 
-  // This is *only* created in classic and single-process mash cases. In
-  // multi-process mash Ash creates the MultiUserWindowManager.
-  std::unique_ptr<ash::MultiUserWindowManagerImpl>
-      ash_multi_user_window_manager_;
-
-  // Only used for windows created for the window-service. For example,
-  // Browser windows when running in mash.
-  ash::mojom::MultiUserWindowManagerAssociatedPtr
-      multi_user_window_manager_mojom_;
-
-  mojo::AssociatedBinding<ash::mojom::MultiUserWindowManagerClient>
-      client_binding_{this};
+  std::unique_ptr<ash::MultiUserWindowManager> ash_multi_user_window_manager_;
 
   DISALLOW_COPY_AND_ASSIGN(MultiUserWindowManagerClientImpl);
 };
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.cc b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.cc
deleted file mode 100644
index 6352813..0000000
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.cc
+++ /dev/null
@@ -1,20 +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 "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h"
-
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h"
-#include "ui/base/ui_base_features.h"
-
-// static
-void MultiUserWindowManagerClientImplTestHelper::FlushBindings() {
-  MultiUserWindowManagerClientImpl* instance =
-      MultiUserWindowManagerClientImpl::instance_;
-  if (!instance)
-    return;
-  if (features::IsUsingWindowService())
-    instance->client_binding_.FlushForTesting();
-  else
-    instance->classic_support_->binding.FlushForTesting();
-}
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h
deleted file mode 100644
index d8c18d3..0000000
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h
+++ /dev/null
@@ -1,18 +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 CHROME_BROWSER_UI_ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_CLIENT_IMPL_TEST_HELPER_H_
-#define CHROME_BROWSER_UI_ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_CLIENT_IMPL_TEST_HELPER_H_
-
-#include "base/macros.h"
-
-class MultiUserWindowManagerClientImplTestHelper {
- public:
-  static void FlushBindings();
-
- private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(MultiUserWindowManagerClientImplTestHelper);
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_MULTI_USER_MULTI_USER_WINDOW_MANAGER_CLIENT_IMPL_TEST_HELPER_H_
diff --git a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_unittest.cc b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_unittest.cc
index a3906346..079e12a 100644
--- a/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_unittest.cc
@@ -42,7 +42,6 @@
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h"
 #include "chrome/browser/ui/ash/session_controller_client.h"
 #include "chrome/browser/ui/ash/session_util.h"
 #include "chrome/browser/ui/ash/test_wallpaper_controller.h"
@@ -62,9 +61,6 @@
 #include "components/user_manager/user_manager.h"
 #include "services/ws/common/util.h"
 #include "ui/aura/client/aura_constants.h"
-#include "ui/aura/env.h"
-#include "ui/aura/mus/window_port_mus.h"
-#include "ui/aura/mus/window_tree_client.h"
 #include "ui/aura/test/env_test_helper.h"
 #include "ui/aura/test/mus/change_completion_waiter.h"
 #include "ui/aura/window.h"
@@ -147,7 +143,6 @@
     fake_user_manager_->SwitchActiveUser(id);
     ash::MultiUserWindowManagerImpl::Get()->OnActiveUserSessionChanged(id);
     aura::test::WaitForAllChangesToComplete();
-    MultiUserWindowManagerClientImplTestHelper::FlushBindings();
   }
 
   // Set up the test environment for this many windows.
@@ -215,7 +210,15 @@
   // Like: "S[B], .." would mean that window#0 is shown and belongs to user B.
   // or "S[B,A], .." would mean that window#0 is shown, belongs to B but is
   // shown by A, and "D,..." would mean that window#0 is deleted.
-  std::string GetStatus();
+  std::string GetStatus() {
+    return GetStatusImpl(/* follow_transients */ false);
+  }
+
+  // Same as GetStatus(), but uses the transient root to determine the owning
+  // account.
+  std::string GetStatusUseTransientOwners() {
+    return GetStatusImpl(/* follow_transients */ true);
+  }
 
   // Returns a test-friendly string format of GetOwnersOfVisibleWindows().
   std::string GetOwnersOfVisibleWindowsAsString();
@@ -267,11 +270,9 @@
     return ash::UserSwitchAnimator::CoversScreen(window);
   }
 
-  void FlushWindowClientBinding() {
-    multi_user_window_manager_client_->client_binding_.FlushForTesting();
-  }
-
  private:
+  std::string GetStatusImpl(bool follow_transients);
+
   chromeos::ScopedStubInstallAttributes test_install_attributes_;
 
   // These get created for each session.
@@ -313,10 +314,6 @@
   EnsureTestUser(AccountId::FromUserEmail("a"));
   EnsureTestUser(AccountId::FromUserEmail("b"));
   EnsureTestUser(AccountId::FromUserEmail("c"));
-
-  // MultiUserWindowManager uses MusClient in single-process mash mode.
-  if (features::IsUsingWindowService())
-    ash_test_helper()->CreateMusClient();
 }
 
 void MultiUserWindowManagerClientImplTest::SetUpForThisManyWindows(
@@ -354,7 +351,8 @@
   chromeos::DeviceSettingsService::Shutdown();
 }
 
-std::string MultiUserWindowManagerClientImplTest::GetStatus() {
+std::string MultiUserWindowManagerClientImplTest::GetStatusImpl(
+    bool follow_transients) {
   std::string s;
   for (size_t i = 0; i < windows_.size(); i++) {
     if (i)
@@ -364,11 +362,14 @@
       continue;
     }
     s += window(i)->IsVisible() ? "S[" : "H[";
-    const AccountId& owner =
-        multi_user_window_manager_client_->GetWindowOwner(window(i));
+    aura::Window* window_to_use_for_owner =
+        follow_transients ? ::wm::GetTransientRoot(window(i)) : window(i);
+    const AccountId& owner = multi_user_window_manager_client_->GetWindowOwner(
+        window_to_use_for_owner);
     s += owner.GetUserEmail();
     const AccountId& presenter =
-        multi_user_window_manager_client_->GetUserPresentingWindow(window(i));
+        multi_user_window_manager_client_->GetUserPresentingWindow(
+            window_to_use_for_owner);
     if (!owner.empty() && owner != presenter) {
       s += ",";
       s += presenter.GetUserEmail();
@@ -843,7 +844,7 @@
   multi_user_window_manager_client()->SetWindowOwner(transient, account_id);
 
   // Both windows are shown and owned by user A.
-  EXPECT_EQ("S[A], S[A]", GetStatus());
+  EXPECT_EQ("S[A], S[A]", GetStatusUseTransientOwners());
 
   // Cleanup.
   ::wm::RemoveTransientChild(parent, transient);
@@ -1618,157 +1619,4 @@
             multi_user_window_manager_client()->GetCurrentUserForTest());
 }
 
-class MultiUserWindowManagerClientImplMashTest
-    : public MultiUserWindowManagerClientImplTest {
- public:
-  MultiUserWindowManagerClientImplMashTest() = default;
-  ~MultiUserWindowManagerClientImplMashTest() override = default;
-
-  // MultiUserWindowManagerClientImplTest:
-  void SetUp() override {
-    original_aura_env_mode_ =
-        aura::test::EnvTestHelper().SetMode(aura::Env::Mode::MUS);
-    feature_list_.InitWithFeatures({::features::kSingleProcessMash}, {});
-    MultiUserWindowManagerClientImplTest::SetUp();
-    // TabletModeController calls to PowerManagerClient with a callback that is
-    // run via a posted task. Run the loop now so that we know the task is
-    // processed. Without this, the task gets processed later on, interfering
-    // with this test.
-    base::RunLoop().RunUntilIdle();
-
-    // This test configures views with mus, which means it triggers some of the
-    // DCHECKs ensuring Shell's Env is used.
-    SetRunningOutsideAsh();
-  }
-  void TearDown() override {
-    MultiUserWindowManagerClientImplTest::TearDown();
-    aura::test::EnvTestHelper().SetMode(original_aura_env_mode_);
-  }
-
- protected:
-  std::unique_ptr<views::Widget> CreateMusWidget() {
-    std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
-    views::Widget::InitParams params;
-    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-    params.bounds = gfx::Rect(0, 0, 200, 200);
-    params.native_widget =
-        views::MusClient::Get()->CreateNativeWidget(params, widget.get());
-    widget->Init(params);
-    widget->Show();
-    EXPECT_EQ(aura::Env::Mode::MUS, widget->GetNativeWindow()->env()->mode());
-    // Flush all messages from the WindowTreeClient to ensure ash has finished
-    // Widget creation.
-    aura::test::WaitForAllChangesToComplete();
-    return widget;
-  }
-
- private:
-  aura::Env::Mode original_aura_env_mode_ = aura::Env::Mode::LOCAL;
-  base::test::ScopedFeatureList feature_list_;
-
-  DISALLOW_COPY_AND_ASSIGN(MultiUserWindowManagerClientImplMashTest);
-};
-
-TEST_F(MultiUserWindowManagerClientImplMashTest,
-       SingleProcessMashWindowOrdering) {
-  SetUpForThisManyWindows(1);
-
-  std::unique_ptr<views::Widget> widget = CreateMusWidget();
-  // Flush all messages from the WindowTreeClient to ensure the client id has
-  // been received.
-  aura::test::WaitForAllChangesToComplete();
-  aura::Window* widget_window_in_ash =
-      ash::window_lookup::GetProxyWindowForClientWindow(
-          widget->GetNativeWindow()->parent());
-  ASSERT_TRUE(widget_window_in_ash);
-  EXPECT_EQ(widget_window_in_ash->parent(), window(0)->parent());
-
-  const AccountId account_id_A(AccountId::FromUserEmail("A"));
-  const AccountId account_id_B(AccountId::FromUserEmail("B"));
-  AddTestUser(account_id_A);
-  AddTestUser(account_id_B);
-  SwitchActiveUser(account_id_A);
-
-  // Set the windows owner.
-  ::wm::ActivationClient* activation_client =
-      ::wm::GetActivationClient(window(0)->GetRootWindow());
-  multi_user_window_manager_client()->SetWindowOwner(window(0), account_id_A);
-  multi_user_window_manager_client()->SetWindowOwner(widget->GetNativeWindow(),
-                                                     account_id_A);
-  aura::test::WaitForAllChangesToComplete();
-
-  // Activate the windows one by one.
-  activation_client->ActivateWindow(widget_window_in_ash);
-  EXPECT_EQ(activation_client->GetActiveWindow(), widget_window_in_ash);
-  activation_client->ActivateWindow(window(0));
-  EXPECT_EQ(activation_client->GetActiveWindow(), window(0));
-
-  aura::Window::Windows mru_list =
-      Shell::Get()->mru_window_tracker()->BuildMruWindowList();
-  EXPECT_EQ(mru_list[0], window(0));
-  EXPECT_EQ(mru_list[1], widget_window_in_ash);
-
-  SwitchActiveUser(account_id_B);
-  EXPECT_EQ(activation_client->GetActiveWindow(), nullptr);
-
-  SwitchActiveUser(account_id_A);
-  EXPECT_EQ(activation_client->GetActiveWindow(), window(0));
-
-  mru_list = Shell::Get()->mru_window_tracker()->BuildMruWindowList();
-  ASSERT_EQ(2u, mru_list.size());
-  EXPECT_EQ(mru_list[0], window(0));
-  EXPECT_TRUE(widget_window_in_ash->Contains(mru_list[1]));
-
-  // Swap the activation order, and retry.
-  activation_client->ActivateWindow(window(0));
-  EXPECT_EQ(activation_client->GetActiveWindow(), window(0));
-  activation_client->ActivateWindow(widget_window_in_ash);
-  EXPECT_EQ(activation_client->GetActiveWindow(), widget_window_in_ash);
-
-  mru_list = Shell::Get()->mru_window_tracker()->BuildMruWindowList();
-  EXPECT_EQ(mru_list[0], widget_window_in_ash);
-  EXPECT_EQ(mru_list[1], window(0));
-
-  SwitchActiveUser(account_id_B);
-  EXPECT_EQ(activation_client->GetActiveWindow(), nullptr);
-
-  SwitchActiveUser(account_id_A);
-  EXPECT_EQ(activation_client->GetActiveWindow(), widget_window_in_ash);
-
-  mru_list = Shell::Get()->mru_window_tracker()->BuildMruWindowList();
-  EXPECT_EQ(mru_list[0], widget_window_in_ash);
-  EXPECT_EQ(mru_list[1], window(0));
-}
-
-TEST_F(MultiUserWindowManagerClientImplMashTest, SetWindowOwner) {
-  SetUpForThisManyWindows(1);
-
-  std::unique_ptr<views::Widget> widget = CreateMusWidget();
-  const AccountId account1(AccountId::FromUserEmail("A"));
-  const AccountId account2(AccountId::FromUserEmail("B"));
-  AddTestUser(account1);
-  AddTestUser(account2);
-  SwitchActiveUser(account1);
-
-  // Make both windows be own by |account1|.
-  multi_user_window_manager_client()->SetWindowOwner(window(0), account1);
-  multi_user_window_manager_client()->SetWindowOwner(widget->GetNativeWindow(),
-                                                     account1);
-  aura::test::WaitForAllChangesToComplete();
-
-  // Switch to account2, which should hide the widget.
-  SwitchActiveUser(account2);
-  EXPECT_FALSE(widget->IsVisible());
-
-  // Show the widget for the current user, which should trigger showing it.
-  multi_user_window_manager_client()->ShowWindowForUser(
-      widget->GetNativeWindow(), account2);
-  aura::test::WaitForAllChangesToComplete();
-  FlushWindowClientBinding();
-  EXPECT_TRUE(widget->IsVisible());
-  EXPECT_EQ(account2,
-            multi_user_window_manager_client()->GetUserPresentingWindow(
-                widget->GetNativeWindow()));
-}
-
 }  // namespace ash
diff --git a/chrome/browser/ui/browser_finder_chromeos_unittest.cc b/chrome/browser/ui/browser_finder_chromeos_unittest.cc
index fa63cd7..dae73940 100644
--- a/chrome/browser/ui/browser_finder_chromeos_unittest.cc
+++ b/chrome/browser/ui/browser_finder_chromeos_unittest.cc
@@ -8,7 +8,6 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/test_browser_window_aura.h"
@@ -124,7 +123,6 @@
       browser->window()->GetNativeWindow(), test_account_id2_);
   // ShowWindowForUser() notifies chrome async. FlushBindings() to ensure all
   // the changes happen.
-  MultiUserWindowManagerClientImplTestHelper::FlushBindings();
   EXPECT_EQ(0u, chrome::GetBrowserCount(profile()));
   EXPECT_FALSE(chrome::FindAnyBrowser(profile(), true));
   EXPECT_FALSE(chrome::FindAnyBrowser(profile(), false));
diff --git a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
index f648336..3ac5ba5 100644
--- a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
@@ -41,27 +41,6 @@
 
 namespace {
 
-bool IsSiteSecure(const content::WebContents* web_contents) {
-  const SecurityStateTabHelper* helper =
-      SecurityStateTabHelper::FromWebContents(web_contents);
-  if (helper) {
-    switch (helper->GetSecurityLevel()) {
-      case security_state::SECURITY_LEVEL_COUNT:
-        NOTREACHED();
-        return false;
-      case security_state::EV_SECURE:
-      case security_state::SECURE:
-      case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
-        return true;
-      case security_state::NONE:
-      case security_state::HTTP_SHOW_WARNING:
-      case security_state::DANGEROUS:
-        return false;
-    }
-  }
-  return false;
-}
-
 // Returns true if |app_url| and |page_url| are the same origin. To avoid
 // breaking Hosted Apps and Bookmark Apps that might redirect to sites in the
 // same domain but with "www.", this returns true if |page_url| is secure and in
@@ -294,14 +273,7 @@
     return base::UTF8ToUTF16(extension->name());
   }
 
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  if (!web_contents)
-    return base::string16();
-
-  content::NavigationEntry* entry =
-      web_contents->GetController().GetVisibleEntry();
-  return entry ? entry->GetTitle() : base::string16();
+  return WebAppBrowserController::GetTitle();
 }
 
 GURL HostedAppBrowserController::GetAppLaunchURL() const {
diff --git a/chrome/browser/ui/manifest_web_app_browser_controller.cc b/chrome/browser/ui/manifest_web_app_browser_controller.cc
index 39ec802..22309952 100644
--- a/chrome/browser/ui/manifest_web_app_browser_controller.cc
+++ b/chrome/browser/ui/manifest_web_app_browser_controller.cc
@@ -4,15 +4,20 @@
 
 #include "chrome/browser/ui/manifest_web_app_browser_controller.h"
 
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ssl/origin_util.h"
+#include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
 #include "content/public/browser/navigation_entry.h"
+#include "content/public/common/url_constants.h"
+#include "extensions/common/constants.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/image/image_skia.h"
 #include "url/gurl.h"
 
 ManifestWebAppBrowserController::ManifestWebAppBrowserController(
     Browser* browser)
-    : WebAppBrowserController(browser) {}
+    : WebAppBrowserController(browser), app_launch_url_(GURL()) {}
 
 ManifestWebAppBrowserController::~ManifestWebAppBrowserController() = default;
 
@@ -21,6 +26,33 @@
 }
 
 bool ManifestWebAppBrowserController::ShouldShowToolbar() const {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Don't show a toolbar until a navigation has occurred.
+  if (!web_contents || web_contents->GetLastCommittedURL().is_empty())
+    return false;
+
+  // Show toolbar if the web_contents is not on a secure origin.
+  if (!IsOriginSecure(app_launch_url_, Profile::FromBrowserContext(
+                                           web_contents->GetBrowserContext())
+                                           ->GetPrefs())) {
+    return true;
+  }
+
+  // Show toolbar if web_contents is not on the same origin as it was originally
+  // launched on.
+  if (!url::IsSameOriginWith(app_launch_url_,
+                             web_contents->GetLastCommittedURL()) ||
+      !url::IsSameOriginWith(app_launch_url_, web_contents->GetVisibleURL())) {
+    return true;
+  }
+
+  // Show toolbar if on a insecure external website. This checks the security
+  // level, different from IsOriginSecure which just checks the origin itself.
+  if (!IsSiteSecure(web_contents))
+    return true;
+
   return false;
 }
 
@@ -46,17 +78,6 @@
   return browser()->GetCurrentPageIcon().AsImageSkia();
 }
 
-base::string16 ManifestWebAppBrowserController::GetTitle() const {
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  if (!web_contents)
-    return base::string16();
-
-  content::NavigationEntry* entry =
-      web_contents->GetController().GetVisibleEntry();
-  return entry ? entry->GetTitle() : base::string16();
-}
-
 std::string ManifestWebAppBrowserController::GetAppShortName() const {
   return std::string();
 }
@@ -66,5 +87,13 @@
 }
 
 GURL ManifestWebAppBrowserController::GetAppLaunchURL() const {
-  return GURL();
+  return app_launch_url_;
+}
+
+void ManifestWebAppBrowserController::OnTabInserted(
+    content::WebContents* contents) {
+  if (app_launch_url_.is_empty())
+    app_launch_url_ = contents->GetURL();
+  WebAppBrowserController::OnTabInserted(contents);
+  UpdateToolbarVisibility(false);
 }
diff --git a/chrome/browser/ui/manifest_web_app_browser_controller.h b/chrome/browser/ui/manifest_web_app_browser_controller.h
index 3329044..c96db39 100644
--- a/chrome/browser/ui/manifest_web_app_browser_controller.h
+++ b/chrome/browser/ui/manifest_web_app_browser_controller.h
@@ -39,13 +39,17 @@
 
   gfx::ImageSkia GetWindowIcon() const override;
 
-  base::string16 GetTitle() const override;
-
   std::string GetAppShortName() const override;
 
   base::string16 GetFormattedUrlOrigin() const override;
 
   GURL GetAppLaunchURL() const override;
+
+ protected:
+  void OnTabInserted(content::WebContents* contents) override;
+
+ private:
+  GURL app_launch_url_;
 };
 
 #endif  // CHROME_BROWSER_UI_MANIFEST_WEB_APP_BROWSER_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
index 2493308..25136a3 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc
@@ -236,7 +236,7 @@
 void AppInfoSummaryPanel::OnPerformAction(views::Combobox* combobox) {
   if (combobox == launch_options_combobox_) {
     SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex(
-        launch_options_combobox_->selected_index()));
+        launch_options_combobox_->GetSelectedIndex()));
   } else {
     NOTREACHED();
   }
diff --git a/chrome/browser/ui/views/autofill/payments/card_unmask_prompt_views.cc b/chrome/browser/ui/views/autofill/payments/card_unmask_prompt_views.cc
index b71d7d71..33456f7 100644
--- a/chrome/browser/ui/views/autofill/payments/card_unmask_prompt_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/card_unmask_prompt_views.cc
@@ -305,10 +305,10 @@
   controller_->OnUnmaskResponse(
       cvc_input_->text(),
       month_input_->visible()
-          ? month_input_->GetTextForRow(month_input_->selected_index())
+          ? month_input_->GetTextForRow(month_input_->GetSelectedIndex())
           : base::string16(),
       year_input_->visible()
-          ? year_input_->GetTextForRow(year_input_->selected_index())
+          ? year_input_->GetTextForRow(year_input_->GetSelectedIndex())
           : base::string16(),
       storage_checkbox_ ? storage_checkbox_->checked() : false);
   return false;
@@ -330,9 +330,9 @@
       year_input_->SetInvalid(false);
       SetRetriableErrorMessage(base::string16());
     }
-  } else if (month_input_->selected_index() !=
+  } else if (month_input_->GetSelectedIndex() !=
                  month_combobox_model_.GetDefaultIndex() &&
-             year_input_->selected_index() !=
+             year_input_->GetSelectedIndex() !=
                  year_combobox_model_.GetDefaultIndex()) {
     month_input_->SetInvalid(true);
     year_input_->SetInvalid(true);
@@ -463,8 +463,8 @@
     return true;
 
   return controller_->InputExpirationIsValid(
-      month_input_->GetTextForRow(month_input_->selected_index()),
-      year_input_->GetTextForRow(year_input_->selected_index()));
+      month_input_->GetTextForRow(month_input_->GetSelectedIndex()),
+      year_input_->GetTextForRow(year_input_->GetSelectedIndex()));
 }
 
 void CardUnmaskPromptViews::ClosePrompt() {
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index 72bf3db..c5598a1f 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -2342,9 +2342,9 @@
   VerifyExpirationDateDropdownsAreVisible();
 
   // Ensure the next year is pre-populated but month is not checked.
-  EXPECT_EQ(0, month_input()->selected_index());
+  EXPECT_EQ(0, month_input()->GetSelectedIndex());
   EXPECT_EQ(base::ASCIIToUTF16(test::NextYear()),
-            year_input()->GetTextForRow(year_input()->selected_index()));
+            year_input()->GetTextForRow(year_input()->GetSelectedIndex()));
 }
 
 // Tests the upload save bubble. Ensures that the bubble surfaces a pair of
@@ -2361,8 +2361,8 @@
 
   // Ensure the December is pre-populated but year is not checked.
   EXPECT_EQ(base::ASCIIToUTF16("12"),
-            month_input()->GetTextForRow(month_input()->selected_index()));
-  EXPECT_EQ(0, year_input()->selected_index());
+            month_input()->GetTextForRow(month_input()->GetSelectedIndex()));
+  EXPECT_EQ(0, year_input()->GetSelectedIndex());
 }
 
 // Tests the upload save bubble. Ensures that the bubble surfaces a pair of
@@ -2379,7 +2379,7 @@
   VerifyExpirationDateDropdownsAreVisible();
 
   // Ensure no pre-populated expiration date.
-  EXPECT_EQ(0, month_input()->selected_index());
+  EXPECT_EQ(0, month_input()->GetSelectedIndex());
   EXPECT_EQ(0, year_input()->GetSelectedRow());
 }
 
@@ -2397,7 +2397,7 @@
 
   // Ensure no pre-populated expiration date.
   EXPECT_EQ(base::ASCIIToUTF16("08"),
-            month_input()->GetTextForRow(month_input()->selected_index()));
+            month_input()->GetTextForRow(month_input()->GetSelectedIndex()));
   EXPECT_EQ(0, year_input()->GetSelectedRow());
 }
 
@@ -2418,9 +2418,9 @@
 
   // Ensure pre-populated expiration date.
   EXPECT_EQ(base::ASCIIToUTF16("03"),
-            month_input()->GetTextForRow(month_input()->selected_index()));
+            month_input()->GetTextForRow(month_input()->GetSelectedIndex()));
   EXPECT_EQ(base::ASCIIToUTF16("2017"),
-            year_input()->GetTextForRow(year_input()->selected_index()));
+            year_input()->GetTextForRow(year_input()->GetSelectedIndex()));
 }
 
 // TODO(crbug.com/884817): Investigate combining local vs. upload tests using a
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_offer_bubble_views.cc b/chrome/browser/ui/views/autofill/payments/save_card_offer_bubble_views.cc
index 7123f3b..4a3c0eb 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_offer_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_offer_bubble_views.cc
@@ -92,10 +92,10 @@
         {cardholder_name_textfield_ ? cardholder_name_textfield_->text()
                                     : base::string16(),
          month_input_dropdown_ ? month_input_dropdown_->model()->GetItemAt(
-                                     month_input_dropdown_->selected_index())
+                                     month_input_dropdown_->GetSelectedIndex())
                                : base::string16(),
          year_input_dropdown_ ? year_input_dropdown_->model()->GetItemAt(
-                                    year_input_dropdown_->selected_index())
+                                    year_input_dropdown_->GetSelectedIndex())
                               : base::string16()});
   }
   return true;
@@ -139,10 +139,10 @@
     DCHECK(!cardholder_name_textfield_);
     int month_value = 0, year_value = 0;
     if (!base::StringToInt(month_input_dropdown_->model()->GetItemAt(
-                               month_input_dropdown_->selected_index()),
+                               month_input_dropdown_->GetSelectedIndex()),
                            &month_value) ||
         !base::StringToInt(year_input_dropdown_->model()->GetItemAt(
-                               year_input_dropdown_->selected_index()),
+                               year_input_dropdown_->GetSelectedIndex()),
                            &year_value)) {
       return false;
     }
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
index a81fe6e..0a0468f 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
@@ -223,7 +223,7 @@
 // views::ComboboxListener -----------------------------------------------------
 
 void BookmarkBubbleView::OnPerformAction(views::Combobox* combobox) {
-  if (combobox->selected_index() + 1 == folder_model()->GetItemCount()) {
+  if (combobox->GetSelectedIndex() + 1 == folder_model()->GetItemCount()) {
     base::RecordAction(UserMetricsAction("BookmarkBubble_EditFromCombobox"));
     ShowEditor();
   }
@@ -324,6 +324,7 @@
       base::RecordAction(
           UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"));
     }
-    folder_model()->MaybeChangeParent(node, parent_combobox_->selected_index());
+    folder_model()->MaybeChangeParent(node,
+                                      parent_combobox_->GetSelectedIndex());
   }
 }
diff --git a/chrome/browser/ui/views/content_setting_bubble_contents.cc b/chrome/browser/ui/views/content_setting_bubble_contents.cc
index 34931765..fdcb02b 100644
--- a/chrome/browser/ui/views/content_setting_bubble_contents.cc
+++ b/chrome/browser/ui/views/content_setting_bubble_contents.cc
@@ -680,5 +680,5 @@
   MediaComboboxModel* model =
       static_cast<MediaComboboxModel*>(combobox->model());
   content_setting_bubble_model_->OnMediaMenuClicked(
-      model->type(), model->GetDevices()[combobox->selected_index()].id);
+      model->type(), model->GetDevices()[combobox->GetSelectedIndex()].id);
 }
diff --git a/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc b/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
index 91344bb7..5d56b24 100644
--- a/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
+++ b/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc
@@ -17,9 +17,11 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
@@ -410,6 +412,10 @@
     expected_page_position_ = value;
   }
 
+  void set_expected_screen_position(const std::string& value) {
+    expected_screen_position_ = value;
+  }
+
   // Returns a matcher that will match a std::string (drag event data - e.g.
   // one returned by DOMDragEventWaiter::WaitForNextMatchingEvent) if it matches
   // the expectations of this DOMDragEventVerifier.
@@ -419,7 +425,8 @@
         FieldMatches("drop_effect", expected_drop_effect_),
         FieldMatches("effect_allowed", expected_effect_allowed_),
         FieldMatches("mime_types", expected_mime_types_),
-        FieldMatches("page_position", expected_page_position_));
+        FieldMatches("page_position", expected_page_position_),
+        FieldMatches("screen_position", expected_screen_position_));
   }
 
  private:
@@ -438,6 +445,7 @@
   std::string expected_mime_types_ = "<no expectation>";
   std::string expected_client_position_ = "<no expectation>";
   std::string expected_page_position_ = "<no expectation>";
+  std::string expected_screen_position_ = "<no expectation>";
 
   DISALLOW_COPY_AND_ASSIGN(DOMDragEventVerifier);
 };
@@ -660,6 +668,16 @@
     return drag_simulator_->SimulateDrop(kMiddleOfRightFrame);
   }
 
+  gfx::Point GetMiddleOfRightFrameInScreenCoords() {
+    aura::Window* window = web_contents()->GetNativeView();
+    aura::client::ScreenPositionClient* screen_position_client =
+        aura::client::GetScreenPositionClient(window->GetRootWindow());
+    gfx::Point screen_position(kMiddleOfRightFrame);
+    if (screen_position_client)
+      screen_position_client->ConvertPointToScreen(window, &screen_position);
+    return screen_position;
+  }
+
  private:
   // Constants with coordinates within content/test/data/drag_and_drop/page.html
   // The precise frame center is at 200,200 and 400,200 coordinates, but slight
@@ -1286,6 +1304,42 @@
                     "dragend"}));
 }
 
+// Test that screenX/screenY for drag updates are in screen coordinates.
+// See https://crbug.com/600402 where we mistook the root window coordinate
+// space for the screen coordinate space.
+IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DragUpdateScreenCoordinates) {
+  // Reposition the window so that the root window coordinate space and the
+  // screen coordinate space are clearly distinct. Otherwise this test would
+  // be inconclusive.
+  // In addition to offsetting the window, use a small window size to avoid
+  // rejection of the new bounds by the system.
+  browser()->window()->SetBounds(gfx::Rect(200, 100, 700, 500));
+  do {
+    base::RunLoop run_loop;
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+    run_loop.Run();
+  } while (browser()->window()->GetBounds().origin() != gfx::Point(200, 100));
+
+  std::string frame_site = use_cross_site_subframe() ? "b.com" : "a.com";
+  ASSERT_TRUE(NavigateToTestPage("a.com"));
+  ASSERT_TRUE(NavigateRightFrame(frame_site, "drop_target.html"));
+
+  const gfx::Point screen_position = GetMiddleOfRightFrameInScreenCoords();
+
+  DOMDragEventVerifier expected_dom_event_data;
+  expected_dom_event_data.set_expected_client_position("(155, 150)");
+  expected_dom_event_data.set_expected_screen_position(
+      base::StringPrintf("(%d, %d)", screen_position.x(), screen_position.y()));
+
+  DOMDragEventWaiter dragover_waiter("dragover", right_frame());
+  ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text"));
+
+  std::string dragover_event;
+  ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event));
+  EXPECT_THAT(dragover_event, expected_dom_event_data.Matches());
+}
+
 // TODO(paulmeyer): Should test the case of navigation happening in the middle
 // of a drag operation, and cross-site drags should be allowed across a
 // navigation.
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 75fc69f6..608af7d 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -624,6 +624,7 @@
 void LocationBarView::OnThemeChanged() {
   tint_ = GetTint();
   SkColor icon_color = GetColor(OmniboxPart::RESULTS_ICON);
+  omnibox_page_action_icon_container_view_->SetIconColor(icon_color);
   for (PageActionIconView* icon_view : page_action_icons_)
     icon_view->SetIconColor(icon_color);
   for (ContentSettingImageView* image_view : content_setting_views_)
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row.cc b/chrome/browser/ui/views/page_info/permission_selector_row.cc
index f39bc8fb..6477463 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row.cc
+++ b/chrome/browser/ui/views/page_info/permission_selector_row.cc
@@ -146,7 +146,7 @@
 }
 
 void PermissionCombobox::OnPerformAction(Combobox* combobox) {
-  model_->OnPerformAction(combobox->selected_index());
+  model_->OnPerformAction(combobox->GetSelectedIndex());
 }
 
 }  // namespace internal
diff --git a/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc b/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
index be99d4e..bcad029 100644
--- a/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
@@ -109,13 +109,13 @@
         view_parent->GetViewByID(EditorViewController::GetInputFieldViewId(
             autofill::CREDIT_CARD_EXP_MONTH)));
     base::string16 month =
-        month_combobox->model()->GetItemAt(month_combobox->selected_index());
+        month_combobox->model()->GetItemAt(month_combobox->GetSelectedIndex());
 
     views::Combobox* year_combobox = static_cast<views::Combobox*>(
         view_parent->GetViewByID(EditorViewController::GetInputFieldViewId(
             autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR)));
     base::string16 year =
-        year_combobox->model()->GetItemAt(year_combobox->selected_index());
+        year_combobox->model()->GetItemAt(year_combobox->GetSelectedIndex());
 
     bool is_expired = IsCardExpired(month, year, app_locale_);
     month_combobox->SetInvalid(is_expired);
@@ -410,7 +410,7 @@
         static_cast<autofill::AddressComboboxModel*>(address_combobox->model());
 
     credit_card_to_edit_->set_billing_address_id(
-        model->GetItemIdentifierAt(address_combobox->selected_index()));
+        model->GetItemIdentifierAt(address_combobox->GetSelectedIndex()));
     if (!is_incognito()) {
       state()->GetPersonalDataManager()->UpdateServerCardMetadata(
           *credit_card_to_edit_);
@@ -442,10 +442,10 @@
           static_cast<autofill::AddressComboboxModel*>(combobox->model());
 
       credit_card.set_billing_address_id(
-          model->GetItemIdentifierAt(combobox->selected_index()));
+          model->GetItemIdentifierAt(combobox->GetSelectedIndex()));
     } else {
       credit_card.SetInfo(autofill::AutofillType(field.second.type),
-                          combobox->GetTextForRow(combobox->selected_index()),
+                          combobox->GetTextForRow(combobox->GetSelectedIndex()),
                           locale);
     }
   }
@@ -739,7 +739,7 @@
     // addresses when choosing them as billing addresses.
     autofill::AddressComboboxModel* model =
         static_cast<autofill::AddressComboboxModel*>(combobox->model());
-    if (model->GetItemIdentifierAt(combobox->selected_index()).empty()) {
+    if (model->GetItemIdentifierAt(combobox->GetSelectedIndex()).empty()) {
       if (error_message) {
         *error_message =
             l10n_util::GetStringUTF16(IDS_PAYMENTS_BILLING_ADDRESS_REQUIRED);
@@ -748,7 +748,7 @@
     }
     return true;
   }
-  return ValidateValue(combobox->GetTextForRow(combobox->selected_index()),
+  return ValidateValue(combobox->GetTextForRow(combobox->GetSelectedIndex()),
                        error_message);
 }
 
diff --git a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
index 4d08650..bf8436ef 100644
--- a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
@@ -283,7 +283,7 @@
           billing_address_combobox->model());
   EXPECT_EQ(
       billing_profile.guid(),
-      model->GetItemIdentifierAt(billing_address_combobox->selected_index()));
+      model->GetItemIdentifierAt(billing_address_combobox->GetSelectedIndex()));
 
   // Select a different billing address.
   SelectBillingAddress(additional_profile.guid());
diff --git a/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc b/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
index 75c7e63..ffb797d 100644
--- a/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
+++ b/chrome/browser/ui/views/payments/cvc_unmask_view_controller.cc
@@ -309,8 +309,8 @@
           dialog()->GetViewByID(static_cast<int>(DialogViewID::CVC_YEAR)));
       DCHECK(year);
 
-      response.exp_month = month->GetTextForRow(month->selected_index());
-      response.exp_year = year->GetTextForRow(year->selected_index());
+      response.exp_month = month->GetTextForRow(month->GetSelectedIndex());
+      response.exp_year = year->GetTextForRow(year->GetSelectedIndex());
     }
     unmask_delegate_->OnUnmaskResponse(response);
   }
@@ -347,9 +347,9 @@
     int month_value = 0;
     int year_value = 0;
     bool parsable =
-        base::StringToInt(month->GetTextForRow(month->selected_index()),
+        base::StringToInt(month->GetTextForRow(month->GetSelectedIndex()),
                           &month_value) &&
-        base::StringToInt(year->GetTextForRow(year->selected_index()),
+        base::StringToInt(year->GetTextForRow(year->GetSelectedIndex()),
                           &year_value);
 
     if (!parsable) {
diff --git a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
index fd34eeb..44745f3 100644
--- a/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
+++ b/chrome/browser/ui/views/payments/payment_request_browsertest_base.cc
@@ -713,7 +713,7 @@
       static_cast<ValidatingCombobox*>(delegate_->dialog_view()->GetViewByID(
           EditorViewController::GetInputFieldViewId(type)));
   DCHECK(combobox);
-  return combobox->model()->GetItemAt(combobox->selected_index());
+  return combobox->model()->GetItemAt(combobox->GetSelectedIndex());
 }
 
 void PaymentRequestBrowserTestBase::SetComboboxValue(
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
index b4ac18cd..d40ea77 100644
--- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
@@ -176,9 +176,10 @@
   EditorViewController::OnPerformAction(sender);
   if (sender->id() != GetInputFieldViewId(autofill::ADDRESS_HOME_COUNTRY))
     return;
-  DCHECK_GE(sender->selected_index(), 0);
-  if (chosen_country_index_ != static_cast<size_t>(sender->selected_index())) {
-    chosen_country_index_ = sender->selected_index();
+  DCHECK_GE(sender->GetSelectedIndex(), 0);
+  if (chosen_country_index_ !=
+      static_cast<size_t>(sender->GetSelectedIndex())) {
+    chosen_country_index_ = sender->GetSelectedIndex();
     failed_to_load_region_data_ = false;
     // View update must be asynchronous to let the combobox finish performing
     // the action.
@@ -254,7 +255,7 @@
 
 bool ShippingAddressEditorViewController::ShippingAddressValidationDelegate::
     IsValidCombobox(views::Combobox* combobox, base::string16* error_message) {
-  return ValidateValue(combobox->GetTextForRow(combobox->selected_index()),
+  return ValidateValue(combobox->GetTextForRow(combobox->GetSelectedIndex()),
                        error_message);
 }
 
@@ -273,7 +274,7 @@
     ComboboxValueChanged(views::Combobox* combobox) {
   base::string16 error_message;
   bool is_valid = ValidateValue(
-      combobox->GetTextForRow(combobox->selected_index()), &error_message);
+      combobox->GetTextForRow(combobox->GetSelectedIndex()), &error_message);
   controller_->DisplayErrorMessageForField(field_.type, error_message);
   return is_valid;
 }
@@ -529,7 +530,8 @@
   // The combobox can be null when saving to temporary profile while updating
   // the view.
   if (combobox) {
-    base::string16 country(combobox->GetTextForRow(combobox->selected_index()));
+    base::string16 country(
+        combobox->GetTextForRow(combobox->GetSelectedIndex()));
     bool success =
         profile->SetInfo(autofill::ADDRESS_HOME_COUNTRY, country, locale);
     LOG_IF(ERROR, !success && !ignore_errors)
@@ -561,13 +563,13 @@
     if (combobox->IsValid()) {
       success = profile->SetInfo(
           field.second.type,
-          combobox->GetTextForRow(combobox->selected_index()), locale);
+          combobox->GetTextForRow(combobox->GetSelectedIndex()), locale);
     } else {
       success = false;
     }
     LOG_IF(ERROR, !success && !ignore_errors)
         << "Can't setinfo(" << field.second.type << ", "
-        << combobox->GetTextForRow(combobox->selected_index());
+        << combobox->GetTextForRow(combobox->GetSelectedIndex());
     if (!success && !ignore_errors)
       return false;
   }
diff --git a/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc b/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
index 767341a..7ceba53 100644
--- a/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
+++ b/chrome/browser/ui/views/select_file_dialog_extension_browsertest.cc
@@ -162,17 +162,6 @@
     extensions::ExtensionBrowserTest::SetUp();
   }
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    // Ash tablet mode does not automatically enable the virtual keyboard, so
-    // force the virtual keyboard via the command line for tablet mode tests.
-    const char* test_name =
-        ::testing::UnitTest::GetInstance()->current_test_info()->name();
-    if (base::StringPiece(test_name).find("_TabletMode") != std::string::npos)
-      command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
-
-    extensions::ExtensionBrowserTest::SetUpCommandLine(command_line);
-  }
-
   void SetUpOnMainThread() override {
     extensions::ExtensionBrowserTest::SetUpOnMainThread();
     CHECK(profile());
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view.cc b/chrome/browser/ui/views/translate/translate_bubble_view.cc
index b7f6693..6d0d346 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view.cc
@@ -515,22 +515,22 @@
   switch (sender_id) {
     case COMBOBOX_ID_SOURCE_LANGUAGE: {
       if (model_->GetOriginalLanguageIndex() ==
-          source_language_combobox_->selected_index()) {
+          source_language_combobox_->GetSelectedIndex()) {
         break;
       }
       model_->UpdateOriginalLanguageIndex(
-          source_language_combobox_->selected_index());
+          source_language_combobox_->GetSelectedIndex());
       UpdateAdvancedView();
       translate::ReportUiAction(translate::SOURCE_LANGUAGE_MENU_CLICKED);
       break;
     }
     case COMBOBOX_ID_TARGET_LANGUAGE: {
       if (model_->GetTargetLanguageIndex() ==
-          target_language_combobox_->selected_index()) {
+          target_language_combobox_->GetSelectedIndex()) {
         break;
       }
       model_->UpdateTargetLanguageIndex(
-          target_language_combobox_->selected_index());
+          target_language_combobox_->GetSelectedIndex());
       UpdateAdvancedView();
       translate::ReportUiAction(translate::TARGET_LANGUAGE_MENU_CLICKED);
       break;
diff --git a/chrome/browser/ui/views/uninstall_view.cc b/chrome/browser/ui/views/uninstall_view.cc
index bd1f281f..45e6891 100644
--- a/chrome/browser/ui/views/uninstall_view.cc
+++ b/chrome/browser/ui/views/uninstall_view.cc
@@ -130,7 +130,7 @@
     user_selection_ = chrome::RESULT_CODE_UNINSTALL_DELETE_PROFILE;
   if (change_default_browser_ && change_default_browser_->checked()) {
     BrowsersMap::const_iterator i = browsers_->begin();
-    std::advance(i, browsers_combo_->selected_index());
+    std::advance(i, browsers_combo_->GetSelectedIndex());
     base::LaunchOptions options;
     options.start_hidden = true;
     base::LaunchProcess(i->second, options);
diff --git a/chrome/browser/ui/web_app_browser_controller.cc b/chrome/browser/ui/web_app_browser_controller.cc
index 2637fb8..9297f4b 100644
--- a/chrome/browser/ui/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_app_browser_controller.cc
@@ -56,6 +56,29 @@
       net::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
 }
 
+// static
+bool WebAppBrowserController::IsSiteSecure(
+    const content::WebContents* web_contents) {
+  const SecurityStateTabHelper* helper =
+      SecurityStateTabHelper::FromWebContents(web_contents);
+  if (helper) {
+    switch (helper->GetSecurityLevel()) {
+      case security_state::SECURITY_LEVEL_COUNT:
+        NOTREACHED();
+        return false;
+      case security_state::EV_SECURE:
+      case security_state::SECURE:
+      case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
+        return true;
+      case security_state::NONE:
+      case security_state::HTTP_SHOW_WARNING:
+      case security_state::DANGEROUS:
+        return false;
+    }
+  }
+  return false;
+}
+
 WebAppBrowserController::WebAppBrowserController(Browser* browser)
     : content::WebContentsObserver(nullptr), browser_(browser) {
   browser->tab_strip_model()->AddObserver(this);
@@ -119,6 +142,17 @@
   return SkColorSetA(*result, SK_AlphaOPAQUE);
 }
 
+base::string16 WebAppBrowserController::GetTitle() const {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  if (!web_contents)
+    return base::string16();
+
+  content::NavigationEntry* entry =
+      web_contents->GetController().GetVisibleEntry();
+  return entry ? entry->GetTitle() : base::string16();
+}
+
 void WebAppBrowserController::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
diff --git a/chrome/browser/ui/web_app_browser_controller.h b/chrome/browser/ui/web_app_browser_controller.h
index a8bbcbf..4e10bc0f 100644
--- a/chrome/browser/ui/web_app_browser_controller.h
+++ b/chrome/browser/ui/web_app_browser_controller.h
@@ -34,6 +34,9 @@
   // Renders |url|'s origin as Unicode.
   static base::string16 FormatUrlOrigin(const GURL& url);
 
+  // Returns whether the site is secure based on content's security level.
+  static bool IsSiteSecure(const content::WebContents* web_contents);
+
   // Returns true if this controller is for an experimental web app browser.
   bool IsForExperimentalWebAppBrowser() const;
 
@@ -63,7 +66,7 @@
   virtual base::Optional<SkColor> GetThemeColor() const;
 
   // Returns the title to be displayed in the window title bar.
-  virtual base::string16 GetTitle() const = 0;
+  virtual base::string16 GetTitle() const;
 
   // Gets the short name of the app.
   virtual std::string GetAppShortName() const = 0;
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc
index 6dbe658..4243362e 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h"
 
+#include "base/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser_dialogs.h"
@@ -11,7 +12,9 @@
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/cellular_setup_resources.h"
 #include "chrome/grit/cellular_setup_resources_map.h"
+#include "chromeos/services/cellular_setup/public/mojom/constants.mojom.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/aura/window.h"
 
 namespace chromeos {
@@ -88,12 +91,21 @@
 
   content::WebUIDataSource::Add(Profile::FromWebUI(web_ui), source);
 
-  // TODO(khorimoto): Add Mojo bindings to this WebUI so that Mojo calls can
-  // occur in JavaScript.
+  // Add Mojo bindings to this WebUI so that Mojo calls can occur in JavaScript.
+  AddHandlerToRegistry(base::BindRepeating(
+      &CellularSetupDialogUI::BindCellularSetup, base::Unretained(this)));
 }
 
 CellularSetupDialogUI::~CellularSetupDialogUI() = default;
 
+void CellularSetupDialogUI::BindCellularSetup(
+    mojom::CellularSetupRequest request) {
+  service_manager::Connector* connector =
+      content::BrowserContext::GetConnectorFor(
+          web_ui()->GetWebContents()->GetBrowserContext());
+  connector->BindInterface(mojom::kServiceName, std::move(request));
+}
+
 }  // namespace cellular_setup
 
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h
index 164c12f..8f79e4d 100644
--- a/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h
+++ b/chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_dialog.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
+#include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.h"
 #include "ui/web_dialogs/web_dialog_ui.h"
 
 namespace chromeos {
@@ -38,6 +39,9 @@
   explicit CellularSetupDialogUI(content::WebUI* web_ui);
   ~CellularSetupDialogUI() override;
 
+ private:
+  void BindCellularSetup(mojom::CellularSetupRequest request);
+
   DISALLOW_COPY_AND_ASSIGN(CellularSetupDialogUI);
 };
 
diff --git a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
index ce641d7..e729a9b 100644
--- a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
@@ -275,6 +275,7 @@
 void OfflineInternalsUIMessageHandler::ScheduleNwakeWithGCMToken(
     base::Value callback_id,
     const std::string& gcm_token) {
+  prefetch_service_->ForceRefreshSuggestions();
   prefetch_service_->GetPrefetchBackgroundTaskHandler()->EnsureTaskScheduled(
       gcm_token);
   ResolveJavascriptCallback(callback_id, base::Value("Scheduled."));
@@ -455,7 +456,8 @@
   offline_pages::prefetch_prefs::SetPrefetchTestingHeader(
       prefs, args->GetList()[0].GetString());
 
-  offline_pages::prefetch_prefs::SetEnabledByServer(prefs, true);
+  if (prefetch_service_)
+    prefetch_service_->SetEnabledByServer(prefs, true);
 }
 
 void OfflineInternalsUIMessageHandler::HandleGetPrefetchTestingHeader(
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 7a21dc8..8e8d77d 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1966,8 +1966,10 @@
      IDS_SETTINGS_PRINTING_CUPS_ADD_PRINTERS_NEARBY_TITLE},
     {"addPrintersManuallyTitle",
      IDS_SETTINGS_PRINTING_CUPS_ADD_PRINTERS_MANUALLY_TITLE},
-    {"selectManufacturerAndModelTitle",
+    {"manufacturerAndModelDialogTitle",
      IDS_SETTINGS_PRINTING_CUPS_SELECT_MANUFACTURER_AND_MODEL_TITLE},
+    {"manufacturerAndModelAdditionalInformation",
+     IDS_SETTINGS_PRINTING_CUPS_MANUFACTURER_MODEL_ADDITIONAL_INFORMATION},
     {"addPrinterButtonText", IDS_SETTINGS_PRINTING_CUPS_ADD_PRINTER_BUTTON_ADD},
     {"printerDetailsAdvanced", IDS_SETTINGS_PRINTING_CUPS_PRINTER_ADVANCED},
     {"printerDetailsA11yLabel",
diff --git a/chrome/browser/ui/webui/snippets_internals/snippets_internals.mojom b/chrome/browser/ui/webui/snippets_internals/snippets_internals.mojom
index fc2b659..b477bc4 100644
--- a/chrome/browser/ui/webui/snippets_internals/snippets_internals.mojom
+++ b/chrome/browser/ui/webui/snippets_internals/snippets_internals.mojom
@@ -61,9 +61,6 @@
   // Download the last suggestions in json form.
   GetLastJson() => (string json);
 
-  // Reset the notification state.
-  ResetNotificationState();
-
   // Get the suggestions by category.
   GetSuggestionsByCategory() => (array<SuggestionCategory> categories);
 
diff --git a/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.cc b/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.cc
index 9102e5dd..dbf6f9d 100644
--- a/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.cc
@@ -4,6 +4,10 @@
 
 #include "chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.h"
 
+#include <set>
+#include <string>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/containers/flat_map.h"
 #include "base/feature_list.h"
@@ -11,7 +15,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/android/ntp/android_content_suggestions_notifier.h"
 #include "chrome/common/pref_names.h"
 #include "components/ntp_snippets/category_info.h"
 #include "components/ntp_snippets/features.h"
@@ -278,16 +281,6 @@
   std::move(callback).Run(json);
 }
 
-void SnippetsInternalsPageHandler::ResetNotificationState() {
-  pref_service_->SetInteger(
-      prefs::kContentSuggestionsConsecutiveIgnoredPrefName, 0);
-  pref_service_->SetInteger(prefs::kContentSuggestionsNotificationsSentCount,
-                            0);
-  pref_service_->SetInteger(prefs::kContentSuggestionsNotificationsSentDay, 0);
-  AndroidContentSuggestionsNotifier().HideAllNotifications(
-      ContentSuggestionsNotificationAction::HIDE_FRONTMOST);
-}
-
 void SnippetsInternalsPageHandler::GetSuggestionsByCategory(
     GetSuggestionsByCategoryCallback callback) {
   CollectDismissedSuggestions(-1, std::move(callback),
diff --git a/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.h b/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.h
index ae81aa2..3f8d809a 100644
--- a/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.h
+++ b/chrome/browser/ui/webui/snippets_internals/snippets_internals_page_handler.h
@@ -5,6 +5,9 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SNIPPETS_INTERNALS_SNIPPETS_INTERNALS_PAGE_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_SNIPPETS_INTERNALS_SNIPPETS_INTERNALS_PAGE_HANDLER_H_
 
+#include <map>
+#include <vector>
+
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "base/timer/timer.h"
@@ -42,7 +45,6 @@
       int64_t,
       FetchSuggestionsInBackgroundCallback) override;
   void GetLastJson(GetLastJsonCallback) override;
-  void ResetNotificationState() override;
   void GetSuggestionsByCategory(GetSuggestionsByCategoryCallback) override;
   void ClearDismissedSuggestions(int64_t) override;
 
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 7107ade..c146179 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1540,24 +1540,6 @@
 
 // Keeps track of sync promo collapsed state in the Other Devices menu.
 const char kNtpCollapsedSyncPromo[] = "ntp.collapsed_sync_promo";
-
-// Tracks whether we should show notifications related to content suggestions.
-const char kContentSuggestionsNotificationsEnabled[] =
-    "ntp.content_suggestions.notifications.enabled";
-
-// Tracks how many notifications the user has ignored, so we can tell when we
-// should stop showing them.
-const char kContentSuggestionsConsecutiveIgnoredPrefName[] =
-    "ntp.content_suggestions.notifications.consecutive_ignored";
-
-// Tracks how many notifications have been sent today, and what day "today" is,
-// as an integer YYYYMMDD, in wall time in the local timezone.
-// If sent_day changes, sent_count is reset to 0. Allows limiting per-day
-// notification count.
-const char kContentSuggestionsNotificationsSentDay[] =
-    "ntp.content_suggestions.notifications.sent_day";
-const char kContentSuggestionsNotificationsSentCount[] =
-    "ntp.content_suggestions.notifications.sent_count";
 #else
 // Tracks whether a field trial to hide shortcuts on the NTP has been activated.
 // It is only activated for fresh installs, and remains active for those clients
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index a7edbfc..7128679 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -533,10 +533,6 @@
 extern const char kNtpCollapsedRecentlyClosedTabs[];
 extern const char kNtpCollapsedSnapshotDocument[];
 extern const char kNtpCollapsedSyncPromo[];
-extern const char kContentSuggestionsNotificationsEnabled[];
-extern const char kContentSuggestionsConsecutiveIgnoredPrefName[];
-extern const char kContentSuggestionsNotificationsSentDay[];
-extern const char kContentSuggestionsNotificationsSentCount[];
 #else
 extern const char kNtpActivateHideShortcutsFieldTrial[];
 extern const char kNtpCustomBackgroundDict[];
diff --git a/chrome/credential_provider/test/BUILD.gn b/chrome/credential_provider/test/BUILD.gn
index f6dfac8..427e79d 100644
--- a/chrome/credential_provider/test/BUILD.gn
+++ b/chrome/credential_provider/test/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/toolchain/toolchain.gni")
 import("//testing/test.gni")
 
 test("gcp_unittests") {
@@ -50,4 +51,9 @@
     "//third_party/pywebsocket/src/mod_pywebsocket/",
     "//third_party/tlslite/",
   ]
+
+  if (is_win && llvm_force_head_revision) {
+    # TODO(https://crbug.com/958955): Fix dupes and remove this flag.
+    ldflags = [ "/FORCE:MultipleRes" ]
+  }
 }
diff --git a/chrome/installer/mac/.style.yapf b/chrome/installer/mac/.style.yapf
new file mode 100644
index 0000000..8f2be1e
--- /dev/null
+++ b/chrome/installer/mac/.style.yapf
@@ -0,0 +1,3 @@
+[style]
+based_on_style = chromium
+indent_width = 4
diff --git a/chrome/installer/mac/signing/.gitignore b/chrome/installer/mac/signing/.gitignore
new file mode 100644
index 0000000..d99229c
--- /dev/null
+++ b/chrome/installer/mac/signing/.gitignore
@@ -0,0 +1 @@
+config.py.inc
diff --git a/chrome/installer/mac/signing/README.md b/chrome/installer/mac/signing/README.md
new file mode 100644
index 0000000..bde360c2
--- /dev/null
+++ b/chrome/installer/mac/signing/README.md
@@ -0,0 +1,30 @@
+# Signing Scripts for Chrome on macOS
+
+This directory contains Python modules that modify the Chrome application bundle for various
+release channels, sign the resulting bundle, package it into a DMG for distribution, and sign the
+resulting DMG.
+
+## Invoking
+
+The scripts are invoked using the driver located at `//chrome/installer/mac/sign_chrome.py`.
+
+The DMG packaging aspect of the process require assets that are only available in a src-internal
+checkout, so these scripts will not fully succeed if invoked from a Chromium-only checkout. The
+signing-only aspects can be run without the DMG packaging by passing `--no-dmg`.
+
+## Running Tests
+
+From within `//chrome/installer/mac`, run:
+
+    python -m unittest discover
+
+If coverage is available (via `pip install coverage`), run:
+
+    coverage3 run -m unittest discover
+    coverage3 html
+
+## Formatting
+
+The code is automatically formatted with YAPF. Run:
+
+    git cl format --python
diff --git a/chrome/installer/mac/signing/__init__.py b/chrome/installer/mac/signing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/chrome/installer/mac/signing/__init__.py
diff --git a/chrome/installer/mac/signing/config.py.in b/chrome/installer/mac/signing/config.py.in
new file mode 100644
index 0000000..1cff0cf
--- /dev/null
+++ b/chrome/installer/mac/signing/config.py.in
@@ -0,0 +1,176 @@
+# @GEN_HEADER@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os.path
+
+from .model import Distribution
+
+class CodeSignConfig(object):
+    """Code sign base configuration object.
+
+    A CodeSignConfig contains properties that provide path component information
+    to locate products for code signing and options that control the code
+    signing process.
+
+    The base configuration is produced from variable injection during the build
+    process. Derived configurations are created for internal signing artifacts
+    and when using |model.Distribution| objects.
+    """
+    def __init__(self, identity, keychain=None):
+        """Creates a CodeSignConfig that will sign the product using the static
+        properties on the class, using the code signing identity passed to the
+        constructor, which is found in the specified keychain.
+
+        Args:
+            identity: The name of the code signing identity to use. This can
+                be any value that `codesign -s <identity>` accepts, like the
+                hex-encoded SHA1 hash of the certificate. Must not be None.
+            keychain: Optional path to the keychain file, in which the signing
+                |identity| will be searched for.
+        """
+        assert identity
+        self._identity = identity
+        self._keychain = keychain
+
+    @property
+    def identity(self):
+        """Returns the code signing identity that will be used to sign the
+        products.
+        """
+        return self._identity
+
+    @property
+    def keychain(self):
+        """Returns the filename of the keychain in which |identity| will be
+        searched for. May be None.
+        """
+        return self._keychain
+
+    @property
+    def app_product(self):
+        """Returns the product name that is used for the outer .app bundle.
+        This is displayed to the user in Finder.
+        """
+        return '@PRODUCT_FULLNAME@'
+
+    @property
+    def product(self):
+        """Returns the branding product name. This can match |app_product|
+        for some release channels. Other release channels may customize
+        app_product, but components internal to the app bundle will still
+        refer to |product|. This is used to locate the build products from
+        the build system, while |app_product| is used when customizing for
+        |model.Distribution| objects.
+        """
+        return '@PRODUCT_FULLNAME@'
+
+    @property
+    def version(self):
+        """Returns the version of the application."""
+        return '@MAJOR@.@MINOR@.@BUILD@.@PATCH@'
+
+    @property
+    def base_bundle_id(self):
+        """Returns the base CFBundleIdentifier that is used for the outer app
+        bundle, and to which sub-component identifiers are appended.
+        """
+        return '@MAC_BUNDLE_ID@'
+
+    @property
+    def optional_parts(self):
+        """Returns a set of part names that are allowed to be missing when
+        signing the contents of the bundle. The part names should reflect the
+        part short name keys in the dictionary returned by signing.get_parts().
+        """
+        # This part requires src-internal, so it is not required for a Chromium
+        # build signing.
+        return set(('libwidevinecdm.dylib',))
+
+    @property
+    def codesign_options_outer_app(self):
+        """Returns the codesign -o flags for the outer app bundle."""
+        return 'restrict'
+
+    @property
+    def codesign_options_helpers(self):
+        """Returns the codesign -o flags for helper executables within the
+        bundle.
+        """
+        return self.codesign_options_outer_app + ',library'
+
+    @property
+    def codesign_options_installer_tools(self):
+        """Returns the codesign -o flags for the installer tools, which
+        are not shipped to end-users.
+        """
+        return self.codesign_options_helpers + ',kill'
+
+    @property
+    def codesign_requirements_basic(self):
+        """Returns the codesign --requirements string that is combined with
+        a designated identifier requirement string of a
+        |model.CodeSignedProduct|. This requirement is applied to all
+        CodeSignedProducts.
+        """
+        return ''
+
+    @property
+    def codesign_requirements_outer_app(self):
+        """Returns the codesign --requirements string for the outer app bundle.
+        This is used in conjunction with |codesign_requirements_basic|."""
+        return ''
+
+    @property
+    def provisioning_profile_basename(self):
+        """Returns the basename of the provisioning profile used to sign the
+        outer app bundle. This file with a .provisionprofile extension is
+        located in the |packaging_dir|.
+        """
+        return None
+
+    @property
+    def dmg_basename(self):
+        """Returns the file basename of the packaged DMG."""
+        return '{}-{}'.format(self.app_product.replace(' ', ''), self.version)
+
+    @property
+    def distributions(self):
+        """Returns a list of |model.Distribution| objects that customize the
+        results of signing. This must contain at least one Distribution, which
+        can be a default Distribution.
+        """
+        return [Distribution()]
+
+    @property
+    def run_spctl_assess(self):
+        """Returns whether the final code signed binary should be assessed by
+        Gatekeeper after signing.
+        """
+        # The base config should not run spctl because the app bundle is
+        # currently signed with resource rules, which are only permitted for
+        # Google Chrome as signed by Google. The internal_config returns True.
+        return False
+
+    # Computed Properties ######################################################
+
+    @property
+    def app_dir(self):
+        """Returns the path to the outer app bundle directory."""
+        return '{.app_product}.app'.format(self)
+
+    @property
+    def resources_dir(self):
+        """Returns the path to the outer app's Resources directory."""
+        return os.path.join(self.app_dir, 'Contents', 'Resources')
+
+    @property
+    def framework_dir(self):
+        """Returns the path to the app's framework directory."""
+        return '{0.app_dir}/Contents/Versions/{0.version}/{0.product} Framework.framework'.format(self)
+
+    @property
+    def packaging_dir(self):
+        """Returns the path to the packaging and installer tools."""
+        return '{.product} Packaging'.format(self)
diff --git a/chrome/installer/mac/signing/model.py b/chrome/installer/mac/signing/model.py
new file mode 100644
index 0000000..6903d7cf
--- /dev/null
+++ b/chrome/installer/mac/signing/model.py
@@ -0,0 +1,255 @@
+# 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.
+"""Signing Model Objects
+
+This module contains classes that encapsulate data about the signing process.
+"""
+
+import os.path
+
+
+class CodeSignedProduct(object):
+    """Represents a build product that will be signed with `codesign(1)`."""
+
+    def __init__(self,
+                 path,
+                 identifier,
+                 options=None,
+                 requirements=None,
+                 identifier_requirement=True,
+                 sign_with_identifier=False,
+                 resource_rules=None,
+                 entitlements=None,
+                 verify_options=None):
+        """A build product to be codesigned.
+
+        Args:
+            path: The path to the product to be signed. This is relative to a
+                work directory containing the build products.
+            identifier: The unique identifier set when code signing. This is
+                only explicitly passed with the `--identifier` flag if
+                |sign_with_identifier| is True.
+            options: Comma-separated string of option names passed to the
+                `codesign -o` flag.
+            requirements: String for additional `--requirements` to pass to the
+                `codesign` command. These are joined with a space to the
+                |config.CodeSignConfig.codesign_requirements_basic| string. See
+                |CodeSignedProduct.requirements_string()| for details.
+            identifier_requirement: If True, a designated identifier requirement
+                based on |identifier| will be inserted into the requirements
+                string. If False, then no designated requirement will be
+                generated based on the identifier.
+            sign_with_identifier: If True, then the identifier will be specified
+                when running the `codesign` command. If False, `codesign` will
+                infer the identifier itself.
+            resource_rules: A file in |Paths.packaging_dir| to specify for
+                `codesign --resource-rules`. macOS has deprecated resource rules
+                and this should not be used for new products.
+            entitlements: File name of the entitlements file to sign the product
+                with. The file should reside in the |Paths.packaging_dir|.
+            verify_options: Flags to pass to `codesign --verify`, from
+                |VerifyOptions|.
+        """
+        self.path = path
+        self.identifier = identifier
+        self.options = options
+        self.requirements = requirements
+        self.identifier_requirement = identifier_requirement
+        self.sign_with_identifier = sign_with_identifier
+        self.resource_rules = resource_rules
+        self.entitlements = entitlements
+        if not VerifyOptions.valid(verify_options):
+            raise ValueError('Invalid VerifyOptions: {}'.format(verify_options))
+        self.verify_options = verify_options
+
+    def requirements_string(self, config):
+        """Produces a full requirements string for the product.
+
+        Args:
+            config: A |config.CodeSignConfig| object.
+
+        Returns:
+            A string for designated requirements of the product, which can be
+            passed to `codesign --requirements`.
+        """
+        reqs = []
+        if self.identifier_requirement:
+            reqs.append('designated => identifier "{identifier}"'.format(
+                identifier=self.identifier))
+        if self.requirements:
+            reqs.append(self.requirements)
+        if config.codesign_requirements_basic:
+            reqs.append(config.codesign_requirements_basic)
+        return ' '.join(reqs)
+
+    def __repr__(self):
+        return 'CodeSignedProduct(identifier={0.identifier}, ' \
+                'options={0.options}, path={0.path})'.format(self)
+
+
+class VerifyOptions(object):
+    """Enum for the options that can be specified when validating the results of
+    code signing.
+
+    These options are passed to `codesign --verify` after the
+    |CodeSignedProduct| has been signed.
+    """
+    DEEP = ('--deep',)
+    NO_STRICT = ('--no-strict',)
+    IGNORE_RESOURCES = ('--ignore-resources',)
+
+    def __init__(self):
+        raise TypeError('VerifyOptions cannot be constructed')
+
+    @classmethod
+    def valid(cls, options):
+        """Tests if the specified |options| are valid.
+
+        Args:
+            options: Iterable of option strings.
+
+        Returns:
+            True if all the options are valid, False if otherwise.
+        """
+        if options is None:
+            return True
+        all_options = cls.DEEP + cls.NO_STRICT + cls.IGNORE_RESOURCES
+        return all([option in all_options for option in options])
+
+
+class Distribution(object):
+    """A Distribution represents a final, signed, and potentially channel-
+    customized Chrome product.
+
+    Channel customization refers to modifying parts of the app bundle structure
+    to have different file names, internal identifiers, and assets.
+    """
+
+    def __init__(self,
+                 channel=None,
+                 branding_code=None,
+                 app_name_fragment=None,
+                 dmg_name_fragment=None,
+                 channel_customize=False):
+        """Creates a new Distribution object. All arguments are optional.
+
+        Args:
+            channel: The release channel for the product.
+            branding_code: A branding code helps track how users acquired the
+                product from various marketing channels.
+            app_name_fragment: If present, this string fragment is appended to
+                the |config.CodeSignConfig.app_product|. This renames the binary
+                and outer app bundle.
+            dmg_name_fragment: If present, this is appended to the
+                |config.CodeSignConfig.dmg_basename| to help differentiate
+                different |branding_code|s.
+            channel_customize: If True, then the product will be modified in
+                several ways:
+                - The |channel| will be appended to the
+                  |config.CodeSignConfig.base_bundle_id|.
+                - The product will be renamed with |app_name_fragment|.
+                - Different assets will be used for icons in the app.
+        """
+        self.channel = channel
+        self.branding_code = branding_code
+        self.app_name_fragment = app_name_fragment
+        self.dmg_name_fragment = dmg_name_fragment
+        self.channel_customize = channel_customize
+
+    def to_config(self, base_config):
+        """Produces a derived |config.CodeSignConfig| for the Distribution.
+
+        Args:
+            base_config: The base CodeSignConfig to derive.
+
+        Returns:
+            A new CodeSignConfig instance that uses information in the
+            Distribution to alter various properties of the |base_config|.
+        """
+        this = self
+
+        class DistributionCodeSignConfig(base_config.__class__):
+
+            @property
+            def base_config(self):
+                return base_config
+
+            @property
+            def app_product(self):
+                if this.channel_customize:
+                    return '{} {}'.format(base_config.app_product,
+                                          this.app_name_fragment)
+                return base_config.app_product
+
+            @property
+            def base_bundle_id(self):
+                base_bundle_id = base_config.base_bundle_id
+                if this.channel_customize:
+                    return base_bundle_id + '.' + this.channel
+                return base_bundle_id
+
+            @property
+            def provisioning_profile_basename(self):
+                profile = base_config.provisioning_profile_basename
+                if profile and this.channel_customize:
+                    return '{}_{}'.format(profile, this.app_name_fragment)
+                return profile
+
+            @property
+            def dmg_basename(self):
+                if this.dmg_name_fragment:
+                    return '{}-{}-{}'.format(
+                        self.app_product.replace(' ', ''), self.version,
+                        this.dmg_name_fragment)
+                return super(DistributionCodeSignConfig, self).dmg_basename
+
+        return DistributionCodeSignConfig(base_config.identity,
+                                          base_config.keychain)
+
+
+class Paths(object):
+    """Paths holds the three file path contexts for signing operations.
+
+    The input directory always remains un-modified.
+    The output directory is where final, signed products are stored.
+    The work directory is set by internal operations.
+    """
+
+    def __init__(self, input, output, work):
+        self._input = input
+        self._output = output
+        self._work = work
+
+    @property
+    def input(self):
+        return self._input
+
+    @property
+    def output(self):
+        return self._output
+
+    @property
+    def work(self):
+        return self._work
+
+    def packaging_dir(self, config):
+        """Returns the path to the product packaging directory, which contains
+        scripts and assets used in signing.
+
+        Args:
+            config: The |config.CodeSignConfig| object.
+
+        Returns:
+            Path to the packaging directory.
+        """
+        return os.path.join(self.input, '{} Packaging'.format(config.product))
+
+    def replace_work(self, new_work):
+        """Creates a new Paths with the same input and output directories, but
+        with |work| set to |new_work|."""
+        return Paths(self.input, self.output, new_work)
+
+    def __repr__(self):
+        return 'Paths(input={0.input}, output={0.output}, ' \
+                'work={0.work})'.format(self)
diff --git a/chrome/installer/mac/signing/test_config.py b/chrome/installer/mac/signing/test_config.py
new file mode 100644
index 0000000..a590d50
--- /dev/null
+++ b/chrome/installer/mac/signing/test_config.py
@@ -0,0 +1,41 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import imp
+
+config = imp.load_source('signing.config', './signing/config.py.in')
+
+
+class TestConfig(config.CodeSignConfig):
+
+    def __init__(self, identity='[IDENTITY]', keychain='[KEYCHAIN]'):
+        super(TestConfig, self).__init__(identity, keychain)
+
+    @property
+    def app_product(self):
+        return 'App Product'
+
+    @property
+    def product(self):
+        return 'Product'
+
+    @property
+    def version(self):
+        return '99.0.9999.99'
+
+    @property
+    def base_bundle_id(self):
+        return 'test.signing.bundle_id'
+
+    @property
+    def optional_parts(self):
+        return set()
+
+    @property
+    def provisioning_profile_basename(self):
+        return 'provisiontest'
+
+    @property
+    def run_spctl_assess(self):
+        return True
diff --git a/chrome/installer/mac/signing/test_model.py b/chrome/installer/mac/signing/test_model.py
new file mode 100644
index 0000000..5c0c8ee7
--- /dev/null
+++ b/chrome/installer/mac/signing/test_model.py
@@ -0,0 +1,136 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+from . import model
+from .test_config import TestConfig
+
+
+class TestCodeSignedProduct(unittest.TestCase):
+
+    def test_requirements_string_identifier(self):
+        product = model.CodeSignedProduct('path/binary', 'binary')
+        self.assertEqual('designated => identifier "binary"',
+                         product.requirements_string(TestConfig()))
+
+    def test_requirements_no_identifier(self):
+        product = model.CodeSignedProduct(
+            'path/binary', 'binary', identifier_requirement=False)
+        self.assertEqual('', product.requirements_string(TestConfig()))
+
+    def test_requirements_product_requirement(self):
+        product = model.CodeSignedProduct(
+            'path/binary', 'binary', requirements='and another requirement')
+        self.assertEqual(
+            'designated => identifier "binary" and another requirement',
+            product.requirements_string(TestConfig()))
+
+    def test_requirements_config_requirement(self):
+
+        class RequirementConfig(TestConfig):
+
+            @property
+            def codesign_requirements_basic(self):
+                return 'and config requirement'
+
+        product = model.CodeSignedProduct(
+            'path/binary', 'binary', requirements='and another requirement')
+        self.assertEqual(
+            'designated => identifier "binary" and another requirement and config requirement',
+            product.requirements_string(RequirementConfig()))
+
+
+class TestVerifyOptions(unittest.TestCase):
+
+    def test_valid_all(self):
+        opts = (
+            model.VerifyOptions.DEEP + model.VerifyOptions.NO_STRICT +
+            model.VerifyOptions.IGNORE_RESOURCES)
+        self.assertTrue(model.VerifyOptions.valid(opts))
+
+    def test_invalid(self):
+        self.assertFalse(model.VerifyOptions.valid(['--whatever']))
+
+
+class TestDistribution(unittest.TestCase):
+
+    def test_no_options(self):
+        base_config = TestConfig()
+        config = model.Distribution().to_config(base_config)
+        self.assertEqual(base_config, config.base_config)
+        self.assertEqual(base_config.app_product, config.app_product)
+        self.assertEqual(base_config.base_bundle_id, config.base_bundle_id)
+        self.assertEqual(base_config.provisioning_profile_basename,
+                         config.provisioning_profile_basename)
+        self.assertEqual(base_config.dmg_basename, config.dmg_basename)
+
+    def test_channel_no_customize(self):
+        base_config = TestConfig()
+        config = model.Distribution(
+            channel='Beta', app_name_fragment='Beta').to_config(base_config)
+        self.assertEqual(base_config.app_product, config.app_product)
+        self.assertEqual(base_config.product, config.product)
+        self.assertEqual(base_config.base_bundle_id, config.base_bundle_id)
+        self.assertEqual(base_config.provisioning_profile_basename,
+                         config.provisioning_profile_basename)
+        self.assertEqual(base_config.dmg_basename, config.dmg_basename)
+
+    def test_channel_customize(self):
+        base_config = TestConfig()
+        config = model.Distribution(
+            channel='beta', app_name_fragment='Beta',
+            channel_customize=True).to_config(base_config)
+        self.assertEqual('App Product Beta', config.app_product)
+        self.assertEqual(base_config.product, config.product)
+        self.assertEqual('test.signing.bundle_id.beta', config.base_bundle_id)
+        self.assertEqual('provisiontest_Beta',
+                         config.provisioning_profile_basename)
+        self.assertEqual('AppProductBeta-99.0.9999.99', config.dmg_basename)
+
+    def test_dmg_basename_no_channel(self):
+        base_config = TestConfig()
+        config = model.Distribution(
+            dmg_name_fragment='Canary').to_config(base_config)
+        self.assertEqual(base_config, config.base_config)
+        self.assertEqual(base_config.app_product, config.app_product)
+        self.assertEqual(base_config.base_bundle_id, config.base_bundle_id)
+        self.assertEqual(base_config.provisioning_profile_basename,
+                         config.provisioning_profile_basename)
+        self.assertEqual('AppProduct-99.0.9999.99-Canary', config.dmg_basename)
+
+    def test_dmg_basename_channel(self):
+        dist = model.Distribution(
+            channel='dev',
+            app_name_fragment='Dev',
+            dmg_name_fragment='Dev',
+            channel_customize=True)
+        config = dist.to_config(TestConfig())
+        self.assertEqual('App Product Dev', config.app_product)
+        self.assertEqual('Product', config.product)
+        self.assertEqual('test.signing.bundle_id.dev', config.base_bundle_id)
+        self.assertEqual('provisiontest_Dev',
+                         config.provisioning_profile_basename)
+        self.assertEqual('AppProductDev-99.0.9999.99-Dev', config.dmg_basename)
+
+
+class TestPaths(unittest.TestCase):
+
+    def test_construct(self):
+        paths = model.Paths('[INPUT]', '[OUTPUT]', '[WORK]')
+        self.assertEqual('[INPUT]', paths.input)
+        self.assertEqual('[OUTPUT]', paths.output)
+        self.assertEqual('[WORK]', paths.work)
+
+    def test_packaging_dir(self):
+        paths = model.Paths('[INPUT]', '[OUTPUT]', '[WORK]')
+        packaging_dir = paths.packaging_dir(TestConfig())
+        self.assertEqual('[INPUT]/Product Packaging', packaging_dir)
+
+    def test_replace_work(self):
+        paths = model.Paths('[INPUT]', '[OUTPUT]', '[WORK]')
+        self.assertEqual('[WORK]', paths.work)
+        paths2 = paths.replace_work('{WORK2}')
+        self.assertEqual('[WORK]', paths.work)
+        self.assertEqual('{WORK2}', paths2.work)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f4431a6..dfc8295 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -8,6 +8,7 @@
 import("//build/config/crypto.gni")
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
+import("//build/toolchain/toolchain.gni")
 import("//build/util/version.gni")
 import("//chrome/chrome_repack_locales.gni")
 import("//chrome/common/features.gni")
@@ -2561,8 +2562,6 @@
     "../browser/android/favicon_helper_unittest.cc",
     "../browser/android/mock_location_settings.cc",
     "../browser/android/mock_location_settings.h",
-    "../browser/android/ntp/content_suggestions_notifier_service_unittest.cc",
-    "../browser/android/ntp/content_suggestions_notifier_unittest.cc",
     "../browser/android/oom_intervention/near_oom_monitor_unittest.cc",
     "../browser/android/oom_intervention/oom_intervention_decider_unittest.cc",
     "../browser/android/password_ui_view_android_unittest.cc",
@@ -3055,6 +3054,10 @@
     if (enable_widevine) {
       sources += [ "../browser/media/widevine_hardware_caps_win_unittest.cc" ]
     }
+    if (llvm_force_head_revision) {
+      # TODO(https://crbug.com/958955): Fix dupes and remove this flag.
+      ldflags = [ "/FORCE:MultipleRes" ]
+    }
   }
 
   if (!is_android) {
@@ -3746,8 +3749,6 @@
       "../browser/ui/ash/media_client_unittest.cc",
       "../browser/ui/ash/multi_user/multi_user_context_menu_chromeos_unittest.cc",
       "../browser/ui/ash/multi_user/multi_user_util_chromeos_unittest.cc",
-      "../browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.cc",
-      "../browser/ui/ash/multi_user/multi_user_window_manager_client_impl_test_helper.h",
       "../browser/ui/ash/multi_user/multi_user_window_manager_client_impl_unittest.cc",
       "../browser/ui/ash/network/mobile_data_notifications_unittest.cc",
       "../browser/ui/ash/network/network_portal_notification_controller_unittest.cc",
@@ -5766,6 +5767,10 @@
       configs -= [ "//build/config/win:default_incremental_linking" ]
       configs +=
           [ "//build/config/win:default_large_module_incremental_linking" ]
+      if (llvm_force_head_revision) {
+        # TODO(https://crbug.com/958955): Fix dupes and remove this flag.
+        ldflags = [ "/FORCE:MultipleRes" ]
+      }
     } else {
       sources -= [ "../app/chrome_version.rc.version" ]
     }
diff --git a/chrome/test/data/drag_and_drop/event_monitoring.js b/chrome/test/data/drag_and_drop/event_monitoring.js
index 5d36fbb3..f49272b 100644
--- a/chrome/test/data/drag_and_drop/event_monitoring.js
+++ b/chrome/test/data/drag_and_drop/event_monitoring.js
@@ -37,6 +37,9 @@
       page_position: safe(function() {
         return "(" + ev.pageX + ", " + ev.pageY + ")";
       }),
+      screen_position: safe(function() {
+        return "(" + ev.screenX + ", " + ev.screenY + ")";
+      }),
       window_name: window.name
     });
   }
diff --git a/chrome/test/data/extensions/theme_ntp_background_image/images/theme_ntp_background.png b/chrome/test/data/extensions/theme_ntp_background_image/images/theme_ntp_background.png
new file mode 100644
index 0000000..a336e74
--- /dev/null
+++ b/chrome/test/data/extensions/theme_ntp_background_image/images/theme_ntp_background.png
Binary files differ
diff --git a/chrome/test/data/extensions/theme_ntp_background_image/manifest.json b/chrome/test/data/extensions/theme_ntp_background_image/manifest.json
new file mode 100644
index 0000000..45d453c
--- /dev/null
+++ b/chrome/test/data/extensions/theme_ntp_background_image/manifest.json
@@ -0,0 +1,15 @@
+{
+"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArRdLT8rOFOhqED9zNVlmywZ5CCYCkVozs0u1+R8taTdSvUiiwavqt6Nqkk6N98Dj7uzBz43pdoqPbFoisu78ggPL+oRLGbQrejzc9dqf5VKvx3cnDlKX+AekZqWAj6QCFWN7kP+rgqqvelDHbhEEUYTVcNwjuSDqVDcgQmJuH2OmfUjdV9EmmRKOqUAPmxsx9u77IT/2cbGyA4oGaKnMSP5lFukmxmKTUyxROuL1ePIhprAr510MDdR6CIONwpF+g32UpXJswcEJ+SG4HuHMKwu0nVRcz4v0Av8dKv7PyShBifspyr1zJXKhTYnI87c7U/7zCeoICWDFq+WGSMrbQwIDAQAB",   
+  "name": "NTP background snowflake theme",
+  "manifest_version": 2,
+   "theme": {
+      "colors": {
+         "frame": [ 158, 210, 242 ],
+         "toolbar": [ 187, 221, 247 ]
+      },
+      "images": {
+         "theme_ntp_background": "images/theme_ntp_background.png"
+      }
+   },
+   "version": "1.4"
+}
diff --git a/chromeos/resources/BUILD.gn b/chromeos/resources/BUILD.gn
index 11e05772..73dfaf70 100644
--- a/chromeos/resources/BUILD.gn
+++ b/chromeos/resources/BUILD.gn
@@ -29,6 +29,7 @@
 
   deps = [
     "//chromeos/components/multidevice/mojom:mojom_js",
+    "//chromeos/services/cellular_setup/public/mojom:mojom_js",
     "//chromeos/services/device_sync/public/mojom:mojom_js",
     "//chromeos/services/multidevice_setup/public/mojom:mojom_js",
   ]
diff --git a/chromeos/resources/cellular_resources.grdp b/chromeos/resources/cellular_resources.grdp
new file mode 100644
index 0000000..628e0d6
--- /dev/null
+++ b/chromeos/resources/cellular_resources.grdp
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+  <include name="IDR_CELLULAR_SETUP_MOJOM_HTML"
+      file="${mojom_root}/chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.html"
+      use_base_dir="false"
+      type="BINDATA"
+      compress="gzip" />
+  <include name="IDR_CELLULAR_SETUP_MOJOM_LITE_JS"
+      file="${mojom_root}/chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom-lite.js"
+      use_base_dir="false"
+      type="BINDATA"
+      compress="gzip" />
+</grit-part>
diff --git a/chromeos/resources/chromeos_resources.grd b/chromeos/resources/chromeos_resources.grd
index 6a0d043..44f4a74 100644
--- a/chromeos/resources/chromeos_resources.grd
+++ b/chromeos/resources/chromeos_resources.grd
@@ -12,6 +12,7 @@
   </outputs>
   <release seq="1">
     <includes>
+      <part file="cellular_resources.grdp" />
       <part file="multidevice_resources.grdp" />
       <if expr="enable_cros_libassistant">
         <part file="assistant_resources.grdp" />
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index e39d2fdb..dd1a57f 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -629,7 +629,6 @@
     "//third_party/libaddressinput:test_support",
     "//third_party/libaddressinput:util",
     "//third_party/libphonenumber",
-    "//third_party/re2:re2",
     "//ui/base",
     "//url",
   ]
diff --git a/components/autofill/core/browser/autofill_download_manager.cc b/components/autofill/core/browser/autofill_download_manager.cc
index fd79ad2..1b9a042 100644
--- a/components/autofill/core/browser/autofill_download_manager.cc
+++ b/components/autofill/core/browser/autofill_download_manager.cc
@@ -60,7 +60,7 @@
     {3314445, 3314448}, {3314854, 3314883},
 };
 
-const size_t kMaxQueryGetSize = 1400;  // 1.25 KiB
+const size_t kMaxQueryGetSize = 1400;  // 1.25KB
 const size_t kAutofillDownloadManagerMaxFormCacheSize = 16;
 const size_t kMaxFieldsPerQueryRequest = 100;
 
@@ -448,22 +448,16 @@
   return upload_request.SerializeToString(payload);
 }
 
-// Gets an API method URL given its type (query or upload), an optional
-// resource ID, and the HTTP method to be used.
+// Gets an API method URL given its type (query or upload) and an optional
+// resource ID.
 // Example usage:
-// * GetAPIMethodUrl(REQUEST_QUERY, "1234", "GET") will return "/v1/pages/1234".
-// * GetAPIMethodUrl(REQUEST_QUERY, "1234", "POST") will return "/v1/pages:get".
-// * GetAPIMethodUrl(REQUEST_UPLOAD, "", "POST") will return "/v1/forms:vote".
+//   * GetAPIMethodUrl(REQUEST_QUERY, "1234") will return "/v1/pages/1234".
+//   * GetAPIMethodUrl(REQUEST_UPLOAD, "") will return "/v1/forms:vote".
 std::string GetAPIMethodUrl(AutofillDownloadManager::RequestType type,
-                            base::StringPiece resource_id,
-                            base::StringPiece method) {
+                            base::StringPiece resource_id) {
   const char* api_method_url;
   if (type == AutofillDownloadManager::REQUEST_QUERY) {
-    if (method == "POST") {
-      api_method_url = "/v1/pages:get";
-    } else {
-      api_method_url = "/v1/pages";
-    }
+    api_method_url = "/v1/pages";
   } else if (type == AutofillDownloadManager::REQUEST_UPLOAD) {
     api_method_url = "/v1/forms:vote";
   } else {
@@ -477,35 +471,6 @@
   return base::StrCat({api_method_url, "/", resource_id});
 }
 
-// Gets HTTP body payload for API POST request.
-std::string GetAPIBodyPayload(const std::string& payload,
-                              AutofillDownloadManager::RequestType type) {
-  // Don't do anything for payloads not related to Query.
-  if (type != AutofillDownloadManager::REQUEST_QUERY) {
-    return payload;
-  }
-  // Wrap query payload in a request proto to interface with API Query method.
-  AutofillPageResourceQueryRequest request;
-  request.set_serialized_request(payload);
-  std::string new_payload;
-  DCHECK(request.SerializeToString(&new_payload))
-      << "could not serialize AutofillPageResourceQueryRequest payload";
-  return new_payload;
-}
-
-// Gets the data payload for API Query (POST and GET).
-bool GetAPIQueryPayload(const AutofillQueryContents& query,
-                        std::string* payload) {
-  std::string serialized_query;
-  if (!CreateApiRequestFromLegacyRequest(query).SerializeToString(
-          &serialized_query)) {
-    return false;
-  }
-  base::Base64UrlEncode(serialized_query,
-                        base::Base64UrlEncodePolicy::INCLUDE_PADDING, payload);
-  return true;
-}
-
 }  // namespace
 
 struct AutofillDownloadManager::FormRequestData {
@@ -577,8 +542,10 @@
 
   // Get the query request payload.
   std::string payload;
-  bool is_payload_serialized = UseApi() ? GetAPIQueryPayload(query, &payload)
-                                        : query.SerializeToString(&payload);
+  bool is_payload_serialized =
+      UseApi()
+          ? CreateApiRequestFromLegacyRequest(query).SerializeToString(&payload)
+          : query.SerializeToString(&payload);
   if (!is_payload_serialized) {
     return false;
   }
@@ -707,27 +674,31 @@
   // ID of the resource to add to the API request URL. Nothing will be added if
   // |resource_id| is empty.
   std::string resource_id;
-  std::string method = "POST";
 
+  // Get the resource id of corresponding webpage when doing a query request.
   if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY) {
-    if (request_data.payload.length() <= kMaxAPIQueryGetSize) {
-      resource_id = request_data.payload;
-      method = "GET";
-      UMA_HISTOGRAM_BOOLEAN("Autofill.Query.ApiUrlIsTooLong", false);
-    } else {
-      UMA_HISTOGRAM_BOOLEAN("Autofill.Query.ApiUrlIsTooLong", true);
+    if (request_data.payload.length() <= kMaxQueryGetSize) {
+      base::Base64UrlEncode(request_data.payload,
+                            base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+                            &resource_id);
     }
-    UMA_HISTOGRAM_BOOLEAN("Autofill.Query.Method", (method == "GET") ? 0 : 1);
+    // Query method is always GET (represented by 0) with API.
+    UMA_HISTOGRAM_BOOLEAN("Autofill.Query.Method", 0);
   }
 
   // Make the canonical URL to query the API, e.g.,
   // https://autofill.googleapis.com/v1/forms/1234?alt=proto.
   GURL url = autofill_server_url_.Resolve(
-      GetAPIMethodUrl(request_data.request_type, resource_id, method));
+      GetAPIMethodUrl(request_data.request_type, resource_id));
 
   // Add the query parameter to set the response format to a serialized proto.
   url = net::AppendQueryParameter(url, "alt", "proto");
 
+  // Determine the HTTP method that should be used.
+  std::string method =
+      (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY)
+          ? "GET"
+          : "POST";
   return std::make_tuple(std::move(url), std::move(method));
 }
 
@@ -743,14 +714,6 @@
       UseApi() ? GetRequestURLAndMethodForApi(request_data)
                : GetRequestURLAndMethod(request_data);
 
-  // Track the URL length for GET queries because the URL length can be in the
-  // thousands when rich metadata is enabled.
-  if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY &&
-      method == "GET") {
-    UMA_HISTOGRAM_COUNTS_100000("Autofill.Query.GetUrlLength",
-                                request_url.spec().length());
-  }
-
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = request_url;
   resource_request->load_flags =
@@ -787,14 +750,10 @@
   simple_loader->SetAllowHttpErrorResults(true);
 
   if (method == "POST") {
-    const std::string& content_type =
+    const std::string content_type =
         UseApi() ? "application/x-protobuf" : "text/proto";
-    const std::string& payload =
-        UseApi()
-            ? GetAPIBodyPayload(request_data.payload, request_data.request_type)
-            : request_data.payload;
     // Attach payload data and add data format header.
-    simple_loader->AttachStringForUpload(payload, content_type);
+    simple_loader->AttachStringForUpload(request_data.payload, content_type);
   }
 
   // Transfer ownership of the loader into url_loaders_. Temporarily hang
diff --git a/components/autofill/core/browser/autofill_download_manager.h b/components/autofill/core/browser/autofill_download_manager.h
index 0a49974..f38c3ae8 100644
--- a/components/autofill/core/browser/autofill_download_manager.h
+++ b/components/autofill/core/browser/autofill_download_manager.h
@@ -31,8 +31,6 @@
 class AutofillDriver;
 class FormStructure;
 
-const size_t kMaxAPIQueryGetSize = 10240;  // 10 KiB
-
 // A helper to make sure that tests which modify the set of active autofill
 // experiments do not interfere with one another.
 struct ScopedActiveAutofillExperiments {
diff --git a/components/autofill/core/browser/autofill_download_manager_unittest.cc b/components/autofill/core/browser/autofill_download_manager_unittest.cc
index 8015361..b6c34b6 100644
--- a/components/autofill/core/browser/autofill_download_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_download_manager_unittest.cc
@@ -52,7 +52,6 @@
 #include "services/network/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/re2/src/re2/re2.h"
 #include "url/third_party/mozilla/url_parse.h"
 
 using base::UTF8ToUTF16;
@@ -118,35 +117,6 @@
   return true;
 }
 
-bool GetAutofillPageResourceQueryRequestFromRequest(
-    network::TestURLLoaderFactory::PendingRequest* loader_request,
-    AutofillPageResourceQueryRequest* query_request) {
-  if (loader_request->request.request_body == nullptr) {
-    return false;
-  }
-
-  std::string request_body_content = GetStringFromDataElements(
-      loader_request->request.request_body->elements());
-  if (!query_request->ParseFromString(request_body_content)) {
-    return false;
-  }
-  return true;
-}
-
-bool DeserializeAutofillPageQueryRequest(base::StringPiece serialized_content,
-                                         AutofillPageQueryRequest* request) {
-  std::string decoded_content;
-  if (!base::Base64UrlDecode(serialized_content,
-                             base::Base64UrlDecodePolicy::REQUIRE_PADDING,
-                             &decoded_content)) {
-    return false;
-  }
-  if (!request->ParseFromString(decoded_content)) {
-    return false;
-  }
-  return true;
-}
-
 }  // namespace
 
 // This tests AutofillDownloadManager. AutofillDownloadManagerTest implements
@@ -529,150 +499,22 @@
   // Inspect the request that the test URL loader sent.
   network::TestURLLoaderFactory::PendingRequest* request =
       test_url_loader_factory_.GetPendingRequest(0);
-
-  // Verify request URL and the data payload it carries.
-  {
-    // This is the URL we expect to query the API. The sub-path right after
-    // "/page" corresponds to the serialized AutofillPageQueryRequest proto
-    // (that we filled forms in) encoded in base64. The Autofill
-    // https://clients1.google.com/ domain URL corresponds to the default domain
-    // used by the download manager, which is invalid, but good for testing.
-    const std::string expected_url =
-        R"(https://clients1.google.com/v1/pages/(.+)\?alt=proto)";
-    std::string encoded_request;
-    ASSERT_TRUE(re2::RE2::FullMatch(request->request.url.spec(), expected_url,
-                                    &encoded_request));
-    AutofillPageQueryRequest request_content;
-    ASSERT_TRUE(
-        DeserializeAutofillPageQueryRequest(encoded_request, &request_content));
-    // Verify form content.
-    ASSERT_EQ(request_content.forms().size(), 1);
-    EXPECT_EQ(request_content.forms(0).signature(),
-              form_structures[0]->form_signature());
-    // Verify field content.
-    ASSERT_EQ(request_content.forms(0).fields().size(), 2);
-    EXPECT_EQ(request_content.forms(0).fields(0).signature(),
-              form_structures[0]->field(0)->GetFieldSignature());
-    EXPECT_EQ(request_content.forms(0).fields(1).signature(),
-              form_structures[0]->field(1)->GetFieldSignature());
-  }
-
-  // Verify API key header.
-  {
-    std::string header_value;
-    EXPECT_TRUE(
-        request->request.headers.GetHeader("X-Goog-Api-Key", &header_value));
-    EXPECT_EQ(header_value, "dummykey");
-  }
-  // Verify binary response header.
-  {
-    std::string header_value;
-    ASSERT_TRUE(request->request.headers.GetHeader(
-        "X-Goog-Encode-Response-If-Executable", &header_value));
-    EXPECT_EQ(header_value, "base64");
-  }
-
-  // Verify response.
-  test_url_loader_factory_.SimulateResponseWithoutRemovingFromPendingList(
-      request, "dummy response");
-  // Upon reception of a suggestions query, we expect OnLoadedServerPredictions
-  // to be called back from the observer and some histograms be incremented.
-  EXPECT_EQ(1U, responses_.size());
-  EXPECT_EQ(responses_.front().type_of_response,
-            AutofillDownloadManagerTest::QUERY_SUCCESSFULL);
-  histogram.ExpectBucketCount("Autofill.Query.WasInCache", CACHE_MISS, 1);
-  histogram.ExpectBucketCount("Autofill.Query.HttpResponseOrErrorCode",
-                              net::HTTP_OK, 1);
-}
-
-TEST_F(AutofillDownloadManagerTest, QueryAPITestWhenTooLongUrl) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures(
-      // Enabled
-      // We want to query the API rather than the legacy server.
-      {features::kAutofillUseApi},
-      // Disabled
-      {});
-
-  // Build the form structures that we want to query.
-  FormData form;
-  FormFieldData field;
-  // Fill a really long field that will bust the request URL size limit of 10
-  // KiB. This is not a lot of memory, hence this should not cause problems for
-  // machines running this test. This will force the fallback to POST.
-  field.name_attribute = base::string16(kMaxAPIQueryGetSize, 'a');
-  field.form_control_type = "text";
-  form.fields.push_back(field);
-
-  std::vector<std::unique_ptr<FormStructure>> form_structures;
-  {
-    auto form_structure = std::make_unique<FormStructure>(form);
-    form_structure->set_is_rich_query_enabled(true);
-    form_structures.push_back(std::move(form_structure));
-  }
-
-  AutofillDownloadManager download_manager(&driver_, this, "dummykey");
-
-  // Start the query request and look if it is successful. No response was
-  // received yet.
-  base::HistogramTester histogram;
-  EXPECT_TRUE(
-      download_manager.StartQueryRequest(ToRawPointerVector(form_structures)));
-
-  // Verify request.
-  // Verify if histograms are right.
-  histogram.ExpectUniqueSample("Autofill.ServerQueryResponse",
-                               AutofillMetrics::QUERY_SENT, 1);
-  // Verify that the logged method is POST.
-  histogram.ExpectUniqueSample("Autofill.Query.Method", METHOD_POST, 1);
-
-  // Get the latest request that the test URL loader sent.
-  network::TestURLLoaderFactory::PendingRequest* request =
-      test_url_loader_factory_.GetPendingRequest(0);
-  // Verify that the POST URL is used when request data too large.
+  // This is the URL we expect to query the API. The sub-path right after
+  // "/page" corresponds to the serialized AutofillPageQueryRequest proto (that
+  // we filled forms in) encoded in base64. The Autofill
+  // https://clients1.google.com/ domain URL corresponds to the default domain
+  // used by the download manager.
   const std::string expected_url = {
-      "https://clients1.google.com/v1/pages:get?alt=proto"};
-  // Verify API key header.
+      "https://clients1.google.com/v1/pages/"
+      "Chc2LjEuMTcxNS4xNDQyL2VuIChHR0xMKRIlCU9O84MyjH9NEgsNeu"
+      "FP4BIAGgAiABILDZxOStASABoAIgAaAA==?"
+      "alt=proto"};
   EXPECT_EQ(request->request.url, expected_url);
-  {
-    std::string header_value;
-    EXPECT_TRUE(
-        request->request.headers.GetHeader("X-Goog-Api-Key", &header_value));
-    EXPECT_EQ(header_value, "dummykey");
-  }
-  // Verify Content-Type header.
-  {
-    std::string header_value;
-    ASSERT_TRUE(
-        request->request.headers.GetHeader("Content-Type", &header_value));
-    EXPECT_EQ(header_value, "application/x-protobuf");
-  }
-  // Verify binary response header.
-  {
-    std::string header_value;
-    ASSERT_TRUE(request->request.headers.GetHeader(
-        "X-Goog-Encode-Response-If-Executable", &header_value));
-    EXPECT_EQ(header_value, "base64");
-  }
-  // Verify content of the POST body data.
-  {
-    AutofillPageResourceQueryRequest query_request;
-    ASSERT_TRUE(GetAutofillPageResourceQueryRequestFromRequest(request,
-                                                               &query_request));
-    AutofillPageQueryRequest request_content;
-    ASSERT_TRUE(DeserializeAutofillPageQueryRequest(
-        query_request.serialized_request(), &request_content));
-    // Verify form content.
-    ASSERT_EQ(request_content.forms().size(), 1);
-    EXPECT_EQ(request_content.forms(0).signature(),
-              form_structures[0]->form_signature());
-    // Verify field content.
-    ASSERT_EQ(request_content.forms(0).fields().size(), 1);
-    EXPECT_EQ(request_content.forms(0).fields(0).signature(),
-              form_structures[0]->field(0)->GetFieldSignature());
-  }
+  std::string api_key_header_value;
+  EXPECT_TRUE(request->request.headers.GetHeader("X-Goog-Api-Key",
+                                                 &api_key_header_value));
+  EXPECT_EQ(api_key_header_value, "dummykey");
 
-  // Verify response.
   test_url_loader_factory_.SimulateResponseWithoutRemovingFromPendingList(
       request, "dummy response");
   // Upon reception of a suggestions query, we expect OnLoadedServerPredictions
diff --git a/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc b/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
index fad316fc..58edc63 100644
--- a/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_dispatcher_impl.cc
@@ -106,8 +106,7 @@
     const std::vector<PrefetchURL>& prefetch_urls) {
   if (!prefetch_prefs::IsEnabled(pref_service_)) {
     if (prefetch_prefs::IsForbiddenCheckDue(pref_service_)) {
-      CheckIfEnabledByServer(service_->GetPrefetchNetworkRequestFactory(),
-                             pref_service_);
+      CheckIfEnabledByServer(pref_service_, service_);
     }
     return;
   }
@@ -255,8 +254,7 @@
 
   std::unique_ptr<Task> generate_page_bundle_task =
       std::make_unique<GeneratePageBundleTask>(
-          this, service_->GetPrefetchStore(), service_->GetPrefetchGCMHandler(),
-          service_->GetCachedGCMToken(),
+          this, service_->GetPrefetchStore(), service_->GetCachedGCMToken(),
           service_->GetPrefetchNetworkRequestFactory(),
           base::BindOnce(
               &PrefetchDispatcherImpl::DidGenerateBundleOrGetOperationRequest,
diff --git a/components/offline_pages/core/prefetch/prefetch_prefs.cc b/components/offline_pages/core/prefetch/prefetch_prefs.cc
index d0437c6f69..8f6aa5a 100644
--- a/components/offline_pages/core/prefetch/prefetch_prefs.cc
+++ b/components/offline_pages/core/prefetch/prefetch_prefs.cc
@@ -25,6 +25,8 @@
 
 const char kUserSettingEnabled[] = "offline_prefetch.enabled";
 const char kBackoff[] = "offline_prefetch.backoff";
+const char kContentSuggestionsNotificationsEnabled[] =
+    "ntp.content_suggestions.notifications.enabled";
 
 void RegisterPrefs(PrefRegistrySimple* registry) {
   registry->RegisterListPref(kBackoff);
@@ -34,6 +36,7 @@
   registry->RegisterStringPref(kPrefetchTestingHeaderPref, std::string());
   registry->RegisterBooleanPref(kEnabledByServer, false);
   registry->RegisterTimePref(kNextForbiddenCheckTimePref, base::Time());
+  registry->RegisterBooleanPref(kContentSuggestionsNotificationsEnabled, true);
 }
 
 void SetPrefetchingEnabledInSettings(PrefService* prefs, bool enabled) {
diff --git a/components/offline_pages/core/prefetch/prefetch_prefs.h b/components/offline_pages/core/prefetch/prefetch_prefs.h
index 373f6551..d9bb1557 100644
--- a/components/offline_pages/core/prefetch/prefetch_prefs.h
+++ b/components/offline_pages/core/prefetch/prefetch_prefs.h
@@ -18,6 +18,7 @@
 
 extern const char kBackoff[];
 extern const char kUserSettingEnabled[];
+extern const char kContentSuggestionsNotificationsEnabled[];
 
 void RegisterPrefs(PrefRegistrySimple* registry);
 
diff --git a/components/offline_pages/core/prefetch/prefetch_service.h b/components/offline_pages/core/prefetch/prefetch_service.h
index d779dc8..53d1b8ee7 100644
--- a/components/offline_pages/core/prefetch/prefetch_service.h
+++ b/components/offline_pages/core/prefetch/prefetch_service.h
@@ -20,6 +20,8 @@
 class ContentSuggestionsService;
 }
 
+class PrefService;
+
 namespace offline_pages {
 class OfflineEventLogger;
 class OfflineMetricsCollector;
@@ -82,6 +84,8 @@
   // suggestion from the Prefetching pipeline and/or the Offline Pages database.
   virtual void RemoveSuggestion(GURL url) = 0;
 
+  // Returns a pointer to the PrefetchGCMHandler. It is not available in reduced
+  // mode.
   virtual PrefetchGCMHandler* GetPrefetchGCMHandler() = 0;
 
   // Obtains the current GCM token from the PrefetchGCMHandler
@@ -92,9 +96,15 @@
   virtual void SetCachedGCMToken(const std::string& gcm_token) = 0;
   virtual const std::string& GetCachedGCMToken() const = 0;
 
+  virtual void SetEnabledByServer(PrefService* pref_service, bool enabled) = 0;
+
   // Internal usage only functions. They will eventually be moved out of this
   // class.
 
+  // Attempt prefetching the current set of suggested articles by pretending
+  // they are new. Can be used to force-start the prefetching pipeline.
+  virtual void ForceRefreshSuggestions() = 0;
+
   // Sub-components that are created and owned by this service.
   // The service manages lifetime, hookup and initialization of Prefetch
   // system that consists of multiple specialized objects, all vended by this
diff --git a/components/offline_pages/core/prefetch/prefetch_service_impl.cc b/components/offline_pages/core/prefetch/prefetch_service_impl.cc
index 7c818d1..ba3643b 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_impl.cc
+++ b/components/offline_pages/core/prefetch/prefetch_service_impl.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/logging.h"
 #include "components/image_fetcher/core/image_fetcher.h"
 #include "components/offline_pages/core/client_id.h"
@@ -18,6 +19,7 @@
 #include "components/offline_pages/core/prefetch/prefetch_gcm_handler.h"
 #include "components/offline_pages/core/prefetch/prefetch_importer.h"
 #include "components/offline_pages/core/prefetch/prefetch_network_request_factory.h"
+#include "components/offline_pages/core/prefetch/prefetch_prefs.h"
 #include "components/offline_pages/core/prefetch/store/prefetch_store.h"
 #include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
 #include "components/offline_pages/core/prefetch/suggestions_provider.h"
@@ -28,7 +30,6 @@
 PrefetchServiceImpl::PrefetchServiceImpl(
     std::unique_ptr<OfflineMetricsCollector> offline_metrics_collector,
     std::unique_ptr<PrefetchDispatcher> dispatcher,
-    std::unique_ptr<PrefetchGCMHandler> gcm_handler,
     std::unique_ptr<PrefetchNetworkRequestFactory> network_request_factory,
     OfflinePageModel* offline_page_model,
     std::unique_ptr<PrefetchStore> prefetch_store,
@@ -41,7 +42,6 @@
     image_fetcher::ImageFetcher* image_fetcher)
     : offline_metrics_collector_(std::move(offline_metrics_collector)),
       prefetch_dispatcher_(std::move(dispatcher)),
-      prefetch_gcm_handler_(std::move(gcm_handler)),
       network_request_factory_(std::move(network_request_factory)),
       offline_page_model_(offline_page_model),
       prefetch_store_(std::move(prefetch_store)),
@@ -55,7 +55,6 @@
       weak_ptr_factory_(this) {
   prefetch_dispatcher_->SetService(this);
   prefetch_downloader_->SetPrefetchService(this);
-  prefetch_gcm_handler_->SetService(this);
   if (suggested_articles_observer_)
     suggested_articles_observer_->SetPrefetchService(this);
 }
@@ -66,26 +65,46 @@
   prefetch_dispatcher_.reset();
 }
 
+void PrefetchServiceImpl::ForceRefreshSuggestions() {
+  if (suggestions_provider_) {
+    // Feed only.
+    NewSuggestionsAvailable();
+  } else {
+    // Zine only.
+    DCHECK(suggested_articles_observer_);
+    suggested_articles_observer_->ConsumeSuggestions();
+  }
+}
+
 void PrefetchServiceImpl::SetCachedGCMToken(const std::string& gcm_token) {
-  gcm_token_ = gcm_token;
+  // This method is passed a cached token that was stored in the job scheduler,
+  // to be used until the PrefetchGCMHandler is created. In some cases, the
+  // PrefetchGCMHandler could have been already created and a fresher token
+  // requested before this function is called. Make sure to not override a
+  // fresher token with a stale one.
+  if (gcm_token_.empty())
+    gcm_token_ = gcm_token;
 }
 
 const std::string& PrefetchServiceImpl::GetCachedGCMToken() const {
+  DCHECK(!gcm_token_.empty()) << "No cached token is set, you should call "
+                                 "PrefetchService::GetGCMToken instead";
   return gcm_token_;
 }
 
 void PrefetchServiceImpl::GetGCMToken(GCMTokenCallback callback) {
   DCHECK(prefetch_gcm_handler_);
   prefetch_gcm_handler_->GetGCMToken(base::AdaptCallbackForRepeating(
-      base::BindOnce(&PrefetchServiceImpl::OnGCMTokenReceived,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+      base::BindOnce(&PrefetchServiceImpl::OnGCMTokenReceived, GetWeakPtr(),
+                     std::move(callback))));
 }
 
 void PrefetchServiceImpl::OnGCMTokenReceived(
     GCMTokenCallback callback,
     const std::string& gcm_token,
     instance_id::InstanceID::Result result) {
-  // Keep the token fresh
+  // TODO(dimich): Add UMA reporting on instance_id::InstanceID::Result.
+  // Keep the cached token fresh
   gcm_token_ = gcm_token;
   std::move(callback).Run(gcm_token);
 }
@@ -105,6 +124,7 @@
   suggested_articles_observer_->SetContentSuggestionsServiceAndObserve(
       content_suggestions);
   thumbnail_fetcher_->SetContentSuggestionsService(content_suggestions);
+  content_suggestions_ = content_suggestions;
 }
 
 void PrefetchServiceImpl::SetSuggestionProvider(
@@ -115,6 +135,16 @@
   suggestions_provider_ = suggestions_provider;
 }
 
+void PrefetchServiceImpl::SetEnabledByServer(PrefService* pref_service,
+                                             bool enabled) {
+  if (enabled == prefetch_prefs::IsEnabledByServer(pref_service))
+    return;
+
+  prefetch_prefs::SetEnabledByServer(pref_service, enabled);
+  if (enabled)
+    ForceRefreshSuggestions();
+}
+
 void PrefetchServiceImpl::NewSuggestionsAvailable() {
   DCHECK(suggestions_provider_);
   prefetch_dispatcher_->NewSuggestionsAvailable(suggestions_provider_);
@@ -134,9 +164,17 @@
 }
 
 PrefetchGCMHandler* PrefetchServiceImpl::GetPrefetchGCMHandler() {
+  DCHECK(prefetch_gcm_handler_);
   return prefetch_gcm_handler_.get();
 }
 
+void PrefetchServiceImpl::SetPrefetchGCMHandler(
+    std::unique_ptr<PrefetchGCMHandler> handler) {
+  DCHECK(!prefetch_gcm_handler_);
+  prefetch_gcm_handler_ = std::move(handler);
+  prefetch_gcm_handler_->SetService(this);
+}
+
 PrefetchNetworkRequestFactory*
 PrefetchServiceImpl::GetPrefetchNetworkRequestFactory() {
   return network_request_factory_.get();
@@ -180,7 +218,12 @@
   return image_fetcher_;
 }
 
+base::WeakPtr<PrefetchServiceImpl> PrefetchServiceImpl::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void PrefetchServiceImpl::Shutdown() {
+  prefetch_gcm_handler_.reset();
   suggested_articles_observer_.reset();
   prefetch_downloader_.reset();
 }
diff --git a/components/offline_pages/core/prefetch/prefetch_service_impl.h b/components/offline_pages/core/prefetch/prefetch_service_impl.h
index ba1acf0..5f44885 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_impl.h
+++ b/components/offline_pages/core/prefetch/prefetch_service_impl.h
@@ -25,7 +25,6 @@
   PrefetchServiceImpl(
       std::unique_ptr<OfflineMetricsCollector> offline_metrics_collector,
       std::unique_ptr<PrefetchDispatcher> dispatcher,
-      std::unique_ptr<PrefetchGCMHandler> gcm_handler,
       std::unique_ptr<PrefetchNetworkRequestFactory> network_request_factory,
       OfflinePageModel* offline_page_model,
       std::unique_ptr<PrefetchStore> prefetch_store,
@@ -50,8 +49,10 @@
   void SetCachedGCMToken(const std::string& gcm_token) override;
   const std::string& GetCachedGCMToken() const override;
   void GetGCMToken(GCMTokenCallback callback) override;
+  void SetEnabledByServer(PrefService* pref_service, bool enabled) override;
 
   // Internal usage only functions.
+  void ForceRefreshSuggestions() override;
   OfflineMetricsCollector* GetOfflineMetricsCollector() override;
   PrefetchDispatcher* GetPrefetchDispatcher() override;
   PrefetchNetworkRequestFactory* GetPrefetchNetworkRequestFactory() override;
@@ -62,6 +63,8 @@
   PrefetchImporter* GetPrefetchImporter() override;
   PrefetchBackgroundTaskHandler* GetPrefetchBackgroundTaskHandler() override;
 
+  void SetPrefetchGCMHandler(std::unique_ptr<PrefetchGCMHandler> handler);
+
   // Thumbnail fetchers. With Feed, GetImageFetcher() is available
   // and GetThumbnailFetcher() is null.
   ThumbnailFetcher* GetThumbnailFetcher() override;
@@ -69,6 +72,8 @@
 
   SuggestedArticlesObserver* GetSuggestedArticlesObserverForTesting() override;
 
+  base::WeakPtr<PrefetchServiceImpl> GetWeakPtr();
+
   // KeyedService implementation:
   void Shutdown() override;
 
@@ -79,10 +84,10 @@
 
   OfflineEventLogger logger_;
   std::string gcm_token_;
+  std::unique_ptr<PrefetchGCMHandler> prefetch_gcm_handler_;
 
   std::unique_ptr<OfflineMetricsCollector> offline_metrics_collector_;
   std::unique_ptr<PrefetchDispatcher> prefetch_dispatcher_;
-  std::unique_ptr<PrefetchGCMHandler> prefetch_gcm_handler_;
   std::unique_ptr<PrefetchNetworkRequestFactory> network_request_factory_;
   OfflinePageModel* offline_page_model_;
   std::unique_ptr<PrefetchStore> prefetch_store_;
@@ -96,6 +101,7 @@
   std::unique_ptr<ThumbnailFetcher> thumbnail_fetcher_;
   // Owned by CachedImageFetcherService.
   image_fetcher::ImageFetcher* image_fetcher_;
+  ntp_snippets::ContentSuggestionsService* content_suggestions_;
 
   // Zine/Feed: only non-null when using Feed.
   SuggestionsProvider* suggestions_provider_ = nullptr;
diff --git a/components/offline_pages/core/prefetch/prefetch_service_test_taco.cc b/components/offline_pages/core/prefetch/prefetch_service_test_taco.cc
index f19bafe6..f0eedd793 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_test_taco.cc
+++ b/components/offline_pages/core/prefetch/prefetch_service_test_taco.cc
@@ -194,14 +194,15 @@
 
 void PrefetchServiceTestTaco::CreatePrefetchService() {
   CHECK(!prefetch_service_);
-  prefetch_service_ = std::make_unique<PrefetchServiceImpl>(
+  auto service = std::make_unique<PrefetchServiceImpl>(
       std::move(metrics_collector_), std::move(dispatcher_),
-      std::move(gcm_handler_), std::move(network_request_factory_),
-      offline_page_model_.get(), std::move(prefetch_store_),
-      std::move(suggested_articles_observer_), std::move(prefetch_downloader_),
-      std::move(prefetch_importer_),
+      std::move(network_request_factory_), offline_page_model_.get(),
+      std::move(prefetch_store_), std::move(suggested_articles_observer_),
+      std::move(prefetch_downloader_), std::move(prefetch_importer_),
       std::move(prefetch_background_task_handler_),
       std::move(thumbnail_fetcher_), thumbnail_image_fetcher_.get());
+  service->SetPrefetchGCMHandler(std::move(gcm_handler_));
+  prefetch_service_ = std::move(service);
 }
 
 std::unique_ptr<PrefetchService>
diff --git a/components/offline_pages/core/prefetch/prefetch_service_test_taco.h b/components/offline_pages/core/prefetch/prefetch_service_test_taco.h
index 6ac2da5..6eca457 100644
--- a/components/offline_pages/core/prefetch/prefetch_service_test_taco.h
+++ b/components/offline_pages/core/prefetch/prefetch_service_test_taco.h
@@ -107,6 +107,9 @@
   std::unique_ptr<PrefetchService> CreateAndReturnPrefetchService();
 
   PrefService* pref_service() const { return pref_service_.get(); }
+  PrefetchNetworkRequestFactory* network_request_factory() const {
+    return network_request_factory_.get();
+  }
 
  private:
   std::unique_ptr<OfflineMetricsCollector> metrics_collector_;
diff --git a/components/offline_pages/core/prefetch/server_forbidden_check_request.cc b/components/offline_pages/core/prefetch/server_forbidden_check_request.cc
index efef3a2..71c59a36 100644
--- a/components/offline_pages/core/prefetch/server_forbidden_check_request.cc
+++ b/components/offline_pages/core/prefetch/server_forbidden_check_request.cc
@@ -6,32 +6,48 @@
 
 #include "base/bind.h"
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/offline_event_logger.h"
 #include "components/offline_pages/core/prefetch/prefetch_prefs.h"
+#include "components/offline_pages/core/prefetch/prefetch_service.h"
 #include "components/offline_pages/core/prefetch/server_forbidden_check_request.h"
 
 namespace offline_pages {
 
 namespace {
 void OnGeneratePageBundleResponse(PrefService* pref_service,
+                                  PrefetchService* prefetch_service,
                                   PrefetchRequestStatus status,
                                   const std::string& operation_name,
                                   const std::vector<RenderPageInfo>& pages) {
   if (status == PrefetchRequestStatus::kSuccess ||
       status == PrefetchRequestStatus::kEmptyRequestSuccess) {
-    // Request succeeded; enable prefetching.
-    prefetch_prefs::SetEnabledByServer(pref_service, true);
+    if (prefetch_service) {
+      if (prefetch_service->GetLogger()) {
+        prefetch_service->GetLogger()->RecordActivity(
+            "Server-enabled check: prefetching allowed by server.");
+      }
+      // Request succeeded; enable prefetching.
+      prefetch_service->SetEnabledByServer(pref_service, true);
+    }
+    return;
   }
   // In the case of some error that isn't ForbiddenByOPS, do nothing and allow
   // the check to be run again.
+  if (prefetch_service && prefetch_service->GetLogger()) {
+    prefetch_service->GetLogger()->RecordActivity(
+        "Server-enabled check: prefetching not allowed by server.");
+  }
 }
 }  // namespace
 
-void CheckIfEnabledByServer(PrefetchNetworkRequestFactory* request_factory,
-                            PrefService* pref_service) {
+void CheckIfEnabledByServer(PrefService* pref_service,
+                            PrefetchService* prefetch_service) {
   // Make a GeneratePageBundle request for no pages.
-  request_factory->MakeGeneratePageBundleRequest(
-      std::vector<std::string>(), std::string(),
-      base::BindOnce(&OnGeneratePageBundleResponse, pref_service));
+  prefetch_service->GetPrefetchNetworkRequestFactory()
+      ->MakeGeneratePageBundleRequest(
+          std::vector<std::string>(), std::string(),
+          base::BindOnce(&OnGeneratePageBundleResponse, pref_service,
+                         prefetch_service));
 }
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/server_forbidden_check_request.h b/components/offline_pages/core/prefetch/server_forbidden_check_request.h
index 7175916..bb05783 100644
--- a/components/offline_pages/core/prefetch/server_forbidden_check_request.h
+++ b/components/offline_pages/core/prefetch/server_forbidden_check_request.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "components/offline_pages/core/prefetch/prefetch_network_request_factory.h"
+#include "components/offline_pages/core/prefetch/prefetch_service.h"
 #include "components/prefs/pref_service.h"
 
 namespace offline_pages {
@@ -19,8 +20,8 @@
 // forbidden, i.e. whether the user has started making requests from an
 // allowed country. This is for checking whether the client is forbidden
 // by making a GeneratePageBundle request with no URLs.
-void CheckIfEnabledByServer(PrefetchNetworkRequestFactory* request_factory,
-                            PrefService* pref_service);
+void CheckIfEnabledByServer(PrefService* pref_service,
+                            PrefetchService* prefetch_service);
 }  // namespace offline_pages
 
 #endif  // COMPONENTS_OFFLINE_PAGES_CORE_PREFETCH_SERVER_FORBIDDEN_CHECK_REQUEST_H_
diff --git a/components/offline_pages/core/prefetch/server_forbidden_check_request_unittest.cc b/components/offline_pages/core/prefetch/server_forbidden_check_request_unittest.cc
index a832cd4..550b561 100644
--- a/components/offline_pages/core/prefetch/server_forbidden_check_request_unittest.cc
+++ b/components/offline_pages/core/prefetch/server_forbidden_check_request_unittest.cc
@@ -6,9 +6,12 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/test/metrics/histogram_tester.h"
+
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/prefetch/fake_suggestions_provider.h"
 #include "components/offline_pages/core/prefetch/prefetch_prefs.h"
 #include "components/offline_pages/core/prefetch/prefetch_request_test_base.h"
+#include "components/offline_pages/core/prefetch/prefetch_service_test_taco.h"
 #include "components/offline_pages/core/prefetch/proto/offline_pages.pb.h"
 #include "components/offline_pages/core/prefetch/proto/operation.pb.h"
 #include "components/offline_pages/core/prefetch/server_forbidden_check_request.h"
@@ -21,36 +24,44 @@
 class PrefService;
 
 namespace offline_pages {
+
 class ServerForbiddenCheckRequestTest : public PrefetchRequestTestBase {
  public:
   ServerForbiddenCheckRequestTest();
-
   void SetUp() override;
 
   void MakeRequest();
 
-  PrefService* prefs() { return &pref_service_; }
-  PrefetchNetworkRequestFactory* request_factory() { return &request_factory_; }
+  PrefService* prefs() { return taco_.pref_service(); }
+  PrefetchNetworkRequestFactory* request_factory() {
+    return taco_.network_request_factory();
+  }
+  PrefetchService* prefetch_service() { return taco_.prefetch_service(); }
 
  private:
-  TestingPrefServiceSimple pref_service_;
-  TestPrefetchNetworkRequestFactory request_factory_;
+  PrefetchServiceTestTaco taco_;
+  FakeSuggestionsProvider suggestions_provider_;
 };
 
 ServerForbiddenCheckRequestTest::ServerForbiddenCheckRequestTest()
-    : request_factory_(shared_url_loader_factory(), prefs()) {}
+    : taco_(PrefetchServiceTestTaco::SuggestionSource::kFeed) {}
 
 void ServerForbiddenCheckRequestTest::SetUp() {
   PrefetchRequestTestBase::SetUp();
 
-  prefetch_prefs::RegisterPrefs(pref_service_.registry());
+  taco_.SetPrefetchNetworkRequestFactory(
+      std::make_unique<TestPrefetchNetworkRequestFactory>(
+          shared_url_loader_factory().get(), prefs()));
+  taco_.CreatePrefetchService();
+  // Feed requirement.
+  prefetch_service()->SetSuggestionProvider(&suggestions_provider_);
 
   // Ensure check will happen.
   prefetch_prefs::SetPrefetchingEnabledInSettings(prefs(), true);
 }
 
 void ServerForbiddenCheckRequestTest::MakeRequest() {
-  CheckIfEnabledByServer(request_factory(), prefs());
+  CheckIfEnabledByServer(prefs(), prefetch_service());
 }
 
 TEST_F(ServerForbiddenCheckRequestTest, StillForbidden) {
diff --git a/components/offline_pages/core/prefetch/stub_prefetch_service.cc b/components/offline_pages/core/prefetch/stub_prefetch_service.cc
index 031e3d1..af8ddba 100644
--- a/components/offline_pages/core/prefetch/stub_prefetch_service.cc
+++ b/components/offline_pages/core/prefetch/stub_prefetch_service.cc
@@ -19,8 +19,11 @@
 void StubPrefetchService::RemoveSuggestion(GURL url) {}
 
 void StubPrefetchService::SetCachedGCMToken(const std::string& gcm_token) {}
+
 void StubPrefetchService::GetGCMToken(GCMTokenCallback callback) {}
 
+void StubPrefetchService::ForceRefreshSuggestions() {}
+
 const std::string& StubPrefetchService::GetCachedGCMToken() const {
   return gcm_token_;
 }
@@ -80,4 +83,7 @@
   return nullptr;
 }
 
+void StubPrefetchService::SetEnabledByServer(PrefService* pref_service,
+                                             bool enabled) {}
+
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/prefetch/stub_prefetch_service.h b/components/offline_pages/core/prefetch/stub_prefetch_service.h
index b05a47b..6beef7f 100644
--- a/components/offline_pages/core/prefetch/stub_prefetch_service.h
+++ b/components/offline_pages/core/prefetch/stub_prefetch_service.h
@@ -23,6 +23,7 @@
   void SetCachedGCMToken(const std::string& gcm_token) override;
   const std::string& GetCachedGCMToken() const override;
   void GetGCMToken(GCMTokenCallback callback) override;
+  void ForceRefreshSuggestions() override;
   PrefetchGCMHandler* GetPrefetchGCMHandler() override;
   OfflineEventLogger* GetLogger() override;
   OfflineMetricsCollector* GetOfflineMetricsCollector() override;
@@ -35,6 +36,7 @@
   ThumbnailFetcher* GetThumbnailFetcher() override;
   OfflinePageModel* GetOfflinePageModel() override;
   image_fetcher::ImageFetcher* GetImageFetcher() override;
+  void SetEnabledByServer(PrefService* pref_service, bool enabled) override;
 
   SuggestedArticlesObserver* GetSuggestedArticlesObserverForTesting() override;
 
diff --git a/components/offline_pages/core/prefetch/suggested_articles_observer.cc b/components/offline_pages/core/prefetch/suggested_articles_observer.cc
index 34b8956e..a5104275 100644
--- a/components/offline_pages/core/prefetch/suggested_articles_observer.cc
+++ b/components/offline_pages/core/prefetch/suggested_articles_observer.cc
@@ -77,12 +77,7 @@
   if (category != ArticlesCategory())
     return;
 
-  std::vector<PrefetchURL> prefetch_urls;
-  if (!GetCurrentSuggestions(&prefetch_urls))
-    return;
-
-  prefetch_service_->GetPrefetchDispatcher()->AddCandidatePrefetchURLs(
-      kSuggestedArticlesNamespace, prefetch_urls);
+  ConsumeSuggestions();
 }
 
 void SuggestedArticlesObserver::OnCategoryStatusChanged(
@@ -124,6 +119,15 @@
   // No need to do anything here, we will just stop getting events.
 }
 
+void SuggestedArticlesObserver::ConsumeSuggestions() {
+  std::vector<PrefetchURL> prefetch_urls;
+  if (!GetCurrentSuggestions(&prefetch_urls))
+    return;
+
+  prefetch_service_->GetPrefetchDispatcher()->AddCandidatePrefetchURLs(
+      kSuggestedArticlesNamespace, prefetch_urls);
+}
+
 std::vector<ntp_snippets::ContentSuggestion>*
 SuggestedArticlesObserver::GetTestingArticles() {
   if (!test_articles_) {
diff --git a/components/offline_pages/core/prefetch/suggested_articles_observer.h b/components/offline_pages/core/prefetch/suggested_articles_observer.h
index 4e369b3..30ebaac 100644
--- a/components/offline_pages/core/prefetch/suggested_articles_observer.h
+++ b/components/offline_pages/core/prefetch/suggested_articles_observer.h
@@ -43,6 +43,9 @@
   void OnFullRefreshRequired() override;
   void ContentSuggestionsServiceShutdown() override;
 
+  // Starts prefetching current suggestions if available.
+  void ConsumeSuggestions();
+
   // Returns a pointer to the list of testing articles. If there is no such
   // list, allocates one before returning the list.  The observer owns the list.
   std::vector<ntp_snippets::ContentSuggestion>* GetTestingArticles();
diff --git a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.cc b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.cc
index 134e726..871c2ad 100644
--- a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.cc
+++ b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.cc
@@ -144,13 +144,11 @@
 GeneratePageBundleTask::GeneratePageBundleTask(
     PrefetchDispatcher* prefetch_dispatcher,
     PrefetchStore* prefetch_store,
-    PrefetchGCMHandler* gcm_handler,
     const std::string& gcm_token,
     PrefetchNetworkRequestFactory* request_factory,
     PrefetchRequestFinishedCallback callback)
     : prefetch_dispatcher_(prefetch_dispatcher),
       prefetch_store_(prefetch_store),
-      gcm_handler_(gcm_handler),
       gcm_token_(gcm_token),
       request_factory_(request_factory),
       callback_(std::move(callback)),
@@ -175,24 +173,7 @@
   DCHECK(!url_and_ids->urls.empty());
   DCHECK_EQ(url_and_ids->urls.size(), url_and_ids->ids.size());
 
-  if (gcm_handler_) {
-    gcm_handler_->GetGCMToken(base::AdaptCallbackForRepeating(
-        base::BindOnce(&GeneratePageBundleTask::GotRegistrationId,
-                       weak_factory_.GetWeakPtr(), std::move(url_and_ids))));
-  } else {
-    DCHECK(!gcm_token_.empty());
-    GotRegistrationId(std::move(url_and_ids), gcm_token_,
-                      instance_id::InstanceID::Result::SUCCESS);
-  }
-}
-
-void GeneratePageBundleTask::GotRegistrationId(
-    std::unique_ptr<UrlAndIds> url_and_ids,
-    const std::string& id,
-    instance_id::InstanceID::Result result) {
-  DCHECK(url_and_ids);
-  // TODO(dimich): Add UMA reporting on instance_id::InstanceID::Result.
-  request_factory_->MakeGeneratePageBundleRequest(url_and_ids->urls, id,
+  request_factory_->MakeGeneratePageBundleRequest(url_and_ids->urls, gcm_token_,
                                                   std::move(callback_));
   prefetch_dispatcher_->GeneratePageBundleRequested(
       std::make_unique<PrefetchDispatcher::IdsVector>(
diff --git a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.h b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.h
index 224e487..1d2edff4 100644
--- a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.h
+++ b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task.h
@@ -10,13 +10,11 @@
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
-#include "components/gcm_driver/instance_id/instance_id.h"
 #include "components/offline_pages/core/prefetch/prefetch_dispatcher.h"
 #include "components/offline_pages/core/prefetch/prefetch_types.h"
 #include "components/offline_pages/task/task.h"
 
 namespace offline_pages {
-class PrefetchGCMHandler;
 class PrefetchNetworkRequestFactory;
 class PrefetchStore;
 
@@ -28,7 +26,6 @@
 
   GeneratePageBundleTask(PrefetchDispatcher* prefetch_dispatcher,
                          PrefetchStore* prefetch_store,
-                         PrefetchGCMHandler* gcm_handler,
                          const std::string& gcm_token,
                          PrefetchNetworkRequestFactory* request_factory,
                          PrefetchRequestFinishedCallback callback);
@@ -39,13 +36,9 @@
 
  private:
   void StartGeneratePageBundle(std::unique_ptr<UrlAndIds> url_and_ids);
-  void GotRegistrationId(std::unique_ptr<UrlAndIds> url_and_ids,
-                         const std::string& id,
-                         instance_id::InstanceID::Result result);
 
   PrefetchDispatcher* prefetch_dispatcher_;
   PrefetchStore* prefetch_store_;
-  PrefetchGCMHandler* gcm_handler_;
   std::string gcm_token_;
   PrefetchNetworkRequestFactory* request_factory_;
   PrefetchRequestFinishedCallback callback_;
diff --git a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task_unittest.cc b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task_unittest.cc
index 43546aa..5dc4496 100644
--- a/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task_unittest.cc
+++ b/components/offline_pages/core/prefetch/tasks/generate_page_bundle_task_unittest.cc
@@ -16,7 +16,6 @@
 #include "components/offline_pages/core/prefetch/store/prefetch_store_utils.h"
 #include "components/offline_pages/core/prefetch/tasks/prefetch_task_test_base.h"
 #include "components/offline_pages/core/prefetch/test_prefetch_dispatcher.h"
-#include "components/offline_pages/core/prefetch/test_prefetch_gcm_handler.h"
 #include "components/offline_pages/core/test_scoped_offline_clock.h"
 #include "components/offline_pages/task/task.h"
 #include "services/network/test/test_utils.h"
@@ -37,13 +36,11 @@
   GeneratePageBundleTaskTest() = default;
   ~GeneratePageBundleTaskTest() override = default;
 
-  TestPrefetchGCMHandler* gcm_handler() { return &gcm_handler_; }
   std::string gcm_token() { return "dummy_gcm_token"; }
 
   TestPrefetchDispatcher* dispatcher() { return &dispatcher_; }
 
  private:
-  TestPrefetchGCMHandler gcm_handler_;
   TestPrefetchDispatcher dispatcher_;
 };
 
@@ -52,16 +49,16 @@
 
   base::MockCallback<PrefetchRequestFinishedCallback> callback;
   RunTask(std::make_unique<GeneratePageBundleTask>(
-      dispatcher(), store(), gcm_handler(), gcm_token(),
-      prefetch_request_factory(), callback.Get()));
+      dispatcher(), store(), gcm_token(), prefetch_request_factory(),
+      callback.Get()));
   EXPECT_EQ(0, dispatcher()->generate_page_bundle_requested);
 }
 
 TEST_F(GeneratePageBundleTaskTest, EmptyTask) {
   base::MockCallback<PrefetchRequestFinishedCallback> callback;
   RunTask(std::make_unique<GeneratePageBundleTask>(
-      dispatcher(), store(), gcm_handler(), gcm_token(),
-      prefetch_request_factory(), callback.Get()));
+      dispatcher(), store(), gcm_token(), prefetch_request_factory(),
+      callback.Get()));
 
   EXPECT_FALSE(prefetch_request_factory()->HasOutstandingRequests());
   auto requested_urls = prefetch_request_factory()->GetAllUrlsRequested();
@@ -104,7 +101,7 @@
 
   clock.Advance(base::TimeDelta::FromHours(1));
 
-  GeneratePageBundleTask task(dispatcher(), store(), gcm_handler(), gcm_token(),
+  GeneratePageBundleTask task(dispatcher(), store(), gcm_token(),
                               prefetch_request_factory(),
                               request_callback.Get());
   RunTask(&task);
diff --git a/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc b/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc
index dfd8109..835dd720 100644
--- a/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc
+++ b/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc
@@ -94,8 +94,7 @@
   // safe" base64 alphabet; plus the padding ('=').
   const char kBase64NonAlphanumericChars[] = "-_=";
 
-  std::string base64_encoded_hash;
-  net::UnescapeBinaryURLComponent(input_hash.as_string(), &base64_encoded_hash);
+  std::string base64_encoded_hash = net::UnescapeBinaryURLComponent(input_hash);
 
   if (!base64_encoded_hash.empty() &&
       CanonicalizeBase64Padding(&base64_encoded_hash) &&
@@ -118,9 +117,8 @@
   // Characters other than alphanumeric that are permitted in the package names.
   const char kPackageNameNonAlphanumericChars[] = "._";
 
-  std::string package_name;
-  net::UnescapeBinaryURLComponent(input_package_name.as_string(),
-                                  &package_name);
+  std::string package_name =
+      net::UnescapeBinaryURLComponent(input_package_name);
 
   // TODO(engedy): We might want to use a regex to check this more throughly.
   if (!package_name.empty() &&
diff --git a/components/policy/core/common/cloud/device_management_service_unittest.cc b/components/policy/core/common/cloud/device_management_service_unittest.cc
index 3517d306..e6993646 100644
--- a/components/policy/core/common/cloud/device_management_service_unittest.cc
+++ b/components/policy/core/common/cloud/device_management_service_unittest.cc
@@ -447,14 +447,11 @@
   std::vector<std::string> GetParams(const std::string& name) {
     std::vector<std::string> results;
     for (const auto& param : params_) {
-      std::string unescaped_name;
-      net::UnescapeBinaryURLComponent(
-          param.first, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE,
-          &unescaped_name);
+      std::string unescaped_name = net::UnescapeBinaryURLComponent(
+          param.first, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
       if (unescaped_name == name) {
-        std::string value;
-        net::UnescapeBinaryURLComponent(
-            param.second, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE, &value);
+        std::string value = net::UnescapeBinaryURLComponent(
+            param.second, net::UnescapeRule::REPLACE_PLUS_WITH_SPACE);
         results.push_back(value);
       }
     }
diff --git a/components/remote_cocoa/DEPS b/components/remote_cocoa/DEPS
new file mode 100644
index 0000000..3a1c916
--- /dev/null
+++ b/components/remote_cocoa/DEPS
@@ -0,0 +1,12 @@
+# Never include ui/views from here!
+include_rules = [
+  "+components/crash/core/common/crash_key.h",
+  "+components/viz/common",
+  "+mojo/public/cpp/bindings",
+  "+ui/accelerated_widget_mac",
+  "+ui/base",
+  "+ui/compositor",
+  "+ui/display",
+  "+ui/events",
+  "+ui/gfx",
+]
diff --git a/components/remote_cocoa/OWNERS b/components/remote_cocoa/OWNERS
new file mode 100644
index 0000000..f2380872
--- /dev/null
+++ b/components/remote_cocoa/OWNERS
@@ -0,0 +1 @@
+file://ui/views/cocoa/OWNERS
diff --git a/components/remote_cocoa/app_shim/BUILD.gn b/components/remote_cocoa/app_shim/BUILD.gn
new file mode 100644
index 0000000..6c4863e
--- /dev/null
+++ b/components/remote_cocoa/app_shim/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+config("app_shim_warnings") {
+  if (is_mac) {
+    # TODO(thakis): Remove this once http://crbug.com/383820 is figured out
+    cflags = [ "-Wno-nonnull" ]
+  }
+}
+
+# This component is for code that is to run in the app shim process.
+component("app_shim") {
+  assert(is_mac)
+
+  configs += [ ":app_shim_warnings" ]
+  sources = [
+    "//ui/views_bridge_mac/alert.h",
+    "//ui/views_bridge_mac/alert.mm",
+    "//ui/views_bridge_mac/bridge_factory_impl.h",
+    "//ui/views_bridge_mac/bridge_factory_impl.mm",
+    "//ui/views_bridge_mac/bridged_content_view.h",
+    "//ui/views_bridge_mac/bridged_content_view.mm",
+    "//ui/views_bridge_mac/bridged_content_view_touch_bar.mm",
+    "//ui/views_bridge_mac/bridged_native_widget_host_helper.h",
+    "//ui/views_bridge_mac/bridged_native_widget_impl.h",
+    "//ui/views_bridge_mac/bridged_native_widget_impl.mm",
+    "//ui/views_bridge_mac/browser_native_widget_window_mac.h",
+    "//ui/views_bridge_mac/browser_native_widget_window_mac.mm",
+    "//ui/views_bridge_mac/cocoa_mouse_capture.h",
+    "//ui/views_bridge_mac/cocoa_mouse_capture.mm",
+    "//ui/views_bridge_mac/cocoa_mouse_capture_delegate.h",
+    "//ui/views_bridge_mac/cocoa_window_move_loop.h",
+    "//ui/views_bridge_mac/cocoa_window_move_loop.mm",
+    "//ui/views_bridge_mac/drag_drop_client.h",
+    "//ui/views_bridge_mac/native_widget_mac_frameless_nswindow.h",
+    "//ui/views_bridge_mac/native_widget_mac_frameless_nswindow.mm",
+    "//ui/views_bridge_mac/native_widget_mac_nswindow.h",
+    "//ui/views_bridge_mac/native_widget_mac_nswindow.mm",
+    "//ui/views_bridge_mac/views_bridge_mac_export.h",
+    "//ui/views_bridge_mac/views_nswindow_delegate.h",
+    "//ui/views_bridge_mac/views_nswindow_delegate.mm",
+    "//ui/views_bridge_mac/views_scrollbar_bridge.h",
+    "//ui/views_bridge_mac/views_scrollbar_bridge.mm",
+    "//ui/views_bridge_mac/window_touch_bar_delegate.h",
+  ]
+  defines = [ "VIEWS_BRIDGE_MAC_IMPLEMENTATION" ]
+  deps = [
+    "//base",
+    "//base:i18n",
+    "//components/crash/core/common",
+    "//mojo/public/cpp/bindings",
+    "//ui/accelerated_widget_mac",
+    "//ui/base",
+    "//ui/base/ime:ime",
+    "//ui/events",
+    "//ui/gfx",
+    "//ui/views_bridge_mac:mojo",
+  ]
+  libs = [
+    "Cocoa.framework",
+    "QuartzCore.framework",
+  ]
+}
diff --git a/components/safe_browsing/db/v4_protocol_manager_util.cc b/components/safe_browsing/db/v4_protocol_manager_util.cc
index 3df953b..ade50e0a 100644
--- a/components/safe_browsing/db/v4_protocol_manager_util.cc
+++ b/components/safe_browsing/db/v4_protocol_manager_util.cc
@@ -45,7 +45,7 @@
   int loop_var = 0;
   do {
     old_size = unescaped_str.size();
-    net::UnescapeBinaryURLComponent(unescaped_str, &unescaped_str);
+    unescaped_str = net::UnescapeBinaryURLComponent(unescaped_str);
   } while (old_size != unescaped_str.size() &&
            ++loop_var <= kMaxLoopIterations);
 
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index 814b4b0..6d95544a 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -276,8 +276,8 @@
          base::ContainsKey(ad_frames_, parent_frame);
 }
 
-bool ContentSubresourceFilterThrottleManager::IsFrameTaggedAsAdForTesting(
-    content::RenderFrameHost* frame_host) const {
+bool ContentSubresourceFilterThrottleManager::IsFrameTaggedAsAd(
+    const content::RenderFrameHost* frame_host) const {
   return base::ContainsKey(ad_frames_, frame_host);
 }
 
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
index fdd519e7..844a553 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h
@@ -80,7 +80,7 @@
   bool CalculateIsAdSubframe(content::RenderFrameHost* frame_host,
                              LoadPolicy load_policy) override;
 
-  bool IsFrameTaggedAsAdForTesting(content::RenderFrameHost* frame_host) const;
+  bool IsFrameTaggedAsAd(const content::RenderFrameHost* frame_host) const;
 
  protected:
   // content::WebContentsObserver:
@@ -168,7 +168,7 @@
   // 3. The RenderFrame declares the frame is an ad (see AdTracker in Blink)
   // 4. It's the result of moving an old ad subframe RFH to a new RFH (e.g.,
   //    OOPIF)
-  std::set<content::RenderFrameHost*> ad_frames_;
+  std::set<const content::RenderFrameHost*> ad_frames_;
 
   content::WebContentsFrameBindingSet<mojom::SubresourceFilterHost> binding_;
 
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
index 334c36bc..0a6e6b9 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager_unittest.cc
@@ -841,9 +841,9 @@
   content::RenderFrameHost* subframe = CreateSubframeWithTestNavigation(
       GURL("https://www.example.com/allowed.html"), main_rfh());
 
-  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAdForTesting(subframe));
+  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAd(subframe));
   throttle_manager()->OnFrameIsAdSubframe(subframe);
-  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAdForTesting(subframe));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(subframe));
 
   SimulateStartAndExpectResult(content::NavigationThrottle::PROCEED);
   subframe =
@@ -880,8 +880,7 @@
 
   // Simulate the render process telling the manager that the frame is an ad.
   throttle_manager()->OnFrameIsAdSubframe(initial_subframe);
-  EXPECT_TRUE(
-      throttle_manager()->IsFrameTaggedAsAdForTesting(initial_subframe));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(initial_subframe));
 
   SimulateStartAndExpectResult(content::NavigationThrottle::PROCEED);
 
@@ -890,9 +889,8 @@
   EXPECT_TRUE(final_subframe);
   EXPECT_NE(initial_subframe, final_subframe);
 
-  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAdForTesting(final_subframe));
-  EXPECT_FALSE(
-      throttle_manager()->IsFrameTaggedAsAdForTesting(initial_subframe));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(final_subframe));
+  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAd(initial_subframe));
   ExpectActivationSignalForFrame(final_subframe, true /* expect_activation */,
                                  true /* is_ad_subframe */);
 }
@@ -927,8 +925,7 @@
       SimulateCommitAndExpectResult(content::NavigationThrottle::PROCEED);
   ExpectActivationSignalForFrame(grandchild_frame, true /* expect_activation */,
                                  true /* is_ad_subframe */);
-  EXPECT_TRUE(
-      throttle_manager()->IsFrameTaggedAsAdForTesting(grandchild_frame));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(grandchild_frame));
 }
 
 TEST_P(ContentSubresourceFilterThrottleManagerTest,
@@ -953,7 +950,7 @@
   // But it should still be activated.
   ExpectActivationSignalForFrame(child, true /* expect_activation */,
                                  true /* is_ad_subframe */);
-  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAdForTesting(child));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(child));
 
   // Create a subframe which is allowed as per ruleset but should still be
   // tagged as ad because of its parent.
@@ -965,7 +962,7 @@
   EXPECT_TRUE(grandchild);
   ExpectActivationSignalForFrame(grandchild, true /* expect_activation */,
                                  true /* is_ad_subframe */);
-  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAdForTesting(grandchild));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(grandchild));
 
   // Verify that a 2nd level nested frame should also be tagged.
   CreateSubframeWithTestNavigation(
@@ -977,7 +974,7 @@
   EXPECT_TRUE(greatGrandchild);
   ExpectActivationSignalForFrame(greatGrandchild, true /* expect_activation */,
                                  true /* is_ad_subframe */);
-  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAdForTesting(greatGrandchild));
+  EXPECT_TRUE(throttle_manager()->IsFrameTaggedAsAd(greatGrandchild));
 
   EXPECT_EQ(0, disallowed_notification_count());
 }
@@ -995,7 +992,7 @@
   EXPECT_TRUE(child);
   ExpectActivationSignalForFrame(child, true /* expect_activation */,
                                  false /* is_ad_subframe */);
-  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAdForTesting(child));
+  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAd(child));
 
   // Create a subframe which is allowed as per ruleset and should not be tagged
   // as ad because its parent is not tagged as well.
@@ -1007,7 +1004,7 @@
   EXPECT_TRUE(grandchild);
   ExpectActivationSignalForFrame(grandchild, true /* expect_activation */,
                                  false /* is_ad_subframe */);
-  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAdForTesting(grandchild));
+  EXPECT_FALSE(throttle_manager()->IsFrameTaggedAsAd(grandchild));
 
   EXPECT_EQ(0, disallowed_notification_count());
 }
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index beb6264..eed9321 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -500,6 +500,7 @@
   sources = [
     "display/bsp_tree_perftest.cc",
     "display/display_perftest.cc",
+    "display/gl_renderer_copier_perftest.cc",
     "display/surface_aggregator_perftest.cc",
   ]
 
diff --git a/components/viz/service/display/gl_renderer_copier_perftest.cc b/components/viz/service/display/gl_renderer_copier_perftest.cc
new file mode 100644
index 0000000..4b22cecf
--- /dev/null
+++ b/components/viz/service/display/gl_renderer_copier_perftest.cc
@@ -0,0 +1,314 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/viz/service/display/gl_renderer_copier.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/lap_timer.h"
+#include "cc/test/pixel_test_utils.h"
+#include "cc/test/test_in_process_context_provider.h"
+#include "components/viz/common/frame_sinks/copy_output_request.h"
+#include "components/viz/common/frame_sinks/copy_output_result.h"
+#include "components/viz/common/frame_sinks/copy_output_util.h"
+#include "components/viz/service/display/gl_renderer.h"
+#include "components/viz/test/paths.h"
+#include "gpu/command_buffer/client/gles2_interface.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace viz {
+
+namespace {
+
+// The size of the source texture or framebuffer.
+constexpr gfx::Size kSourceSize = gfx::Size(240, 120);
+
+// In order to test coordinate calculations and Y-flipping, the tests will issue
+// copy requests for a small region just to the right and below the center of
+// the entire source texture/framebuffer.
+constexpr gfx::Rect kRequestArea = gfx::Rect(kSourceSize.width() / 2,
+                                             kSourceSize.height() / 2,
+                                             kSourceSize.width() / 4,
+                                             kSourceSize.height() / 4);
+
+base::FilePath GetTestFilePath(const base::FilePath::CharType* basename) {
+  base::FilePath test_dir;
+  base::PathService::Get(Paths::DIR_TEST_DATA, &test_dir);
+  return test_dir.Append(base::FilePath(basename));
+}
+
+}  // namespace
+
+class GLRendererCopierPerfTest : public testing::Test {
+ public:
+  GLRendererCopierPerfTest() {
+    auto context_provider =
+        base::MakeRefCounted<cc::TestInProcessContextProvider>(
+            /*enable_oop_rasterization=*/false, /*support_locking=*/false);
+    gpu::ContextResult result = context_provider->BindToCurrentThread();
+    DCHECK_EQ(result, gpu::ContextResult::kSuccess);
+    gl_ = context_provider->ContextGL();
+    texture_deleter_ =
+        std::make_unique<TextureDeleter>(base::ThreadTaskRunnerHandle::Get());
+    copier_ = std::make_unique<GLRendererCopier>(std::move(context_provider),
+                                                 texture_deleter_.get());
+  }
+
+  void TearDown() override {
+    DeleteSourceFramebuffer();
+    DeleteSourceTexture();
+    copier_.reset();
+    texture_deleter_.reset();
+  }
+
+  gfx::Rect DrawToWindowSpace(const gfx::Rect& draw_rect, bool flipped_source) {
+    gfx::Rect window_rect = draw_rect;
+    if (flipped_source)
+      window_rect.set_y(kSourceSize.height() - window_rect.bottom());
+    return window_rect;
+  }
+
+  // Creates a packed RGBA (bytes_per_pixel=4) bitmap in OpenGL byte/row order
+  // from the given SkBitmap.
+  std::unique_ptr<uint8_t[]> CreateGLPixelsFromSkBitmap(SkBitmap bitmap,
+                                                        bool flipped_source) {
+    // |bitmap| could be of any color type (and is usually BGRA). Convert it to
+    // a RGBA bitmap in the GL byte order.
+    SkBitmap rgba_bitmap;
+    rgba_bitmap.allocPixels(SkImageInfo::Make(bitmap.width(), bitmap.height(),
+                                              kRGBA_8888_SkColorType,
+                                              kPremul_SkAlphaType));
+    SkPixmap pixmap;
+    const bool success =
+        bitmap.peekPixels(&pixmap) && rgba_bitmap.writePixels(pixmap, 0, 0);
+    CHECK(success);
+
+    // Copy the RGBA bitmap into a raw byte array, reversing the row order and
+    // maybe stripping-out the alpha channel.
+    const int bytes_per_pixel = 4;
+    std::unique_ptr<uint8_t[]> pixels(
+        new uint8_t[rgba_bitmap.width() * rgba_bitmap.height() *
+                    bytes_per_pixel]);
+    for (int y = 0; y < rgba_bitmap.height(); ++y) {
+      const uint8_t* src = static_cast<uint8_t*>(rgba_bitmap.getAddr(0, y));
+      const int flipped_y = flipped_source ? rgba_bitmap.height() - y - 1 : y;
+      uint8_t* dest =
+          pixels.get() + flipped_y * rgba_bitmap.width() * bytes_per_pixel;
+      for (int x = 0; x < rgba_bitmap.width(); ++x) {
+        *(dest++) = *(src++);
+        *(dest++) = *(src++);
+        *(dest++) = *(src++);
+        if (bytes_per_pixel == 4)
+          *(dest++) = *(src++);
+        else
+          ++src;
+      }
+    }
+
+    return pixels;
+  }
+
+  GLuint CreateSourceTexture(bool flipped_source) {
+    CHECK_EQ(0u, source_texture_);
+    SkBitmap source_bitmap;
+    cc::ReadPNGFile(GetTestFilePath(FILE_PATH_LITERAL("16_color_rects.png")),
+                    &source_bitmap);
+    source_bitmap.setImmutable();
+    gl_->GenTextures(1, &source_texture_);
+    gl_->BindTexture(GL_TEXTURE_2D, source_texture_);
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    gl_->TexImage2D(
+        GL_TEXTURE_2D, 0, static_cast<GLenum>(GL_RGBA), kSourceSize.width(),
+        kSourceSize.height(), 0, static_cast<GLenum>(GL_RGBA), GL_UNSIGNED_BYTE,
+        CreateGLPixelsFromSkBitmap(source_bitmap, flipped_source).get());
+    gl_->BindTexture(GL_TEXTURE_2D, 0);
+    return source_texture_;
+  }
+
+  void DeleteSourceTexture() {
+    if (source_texture_ != 0) {
+      gl_->DeleteTextures(1, &source_texture_);
+      source_texture_ = 0;
+    }
+  }
+
+  void CreateAndBindSourceFramebuffer(GLuint texture) {
+    ASSERT_EQ(0u, source_framebuffer_);
+    gl_->GenFramebuffers(1, &source_framebuffer_);
+    gl_->BindFramebuffer(GL_FRAMEBUFFER, source_framebuffer_);
+    gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                              GL_TEXTURE_2D, texture, 0);
+  }
+
+  void DeleteSourceFramebuffer() {
+    if (source_framebuffer_ != 0) {
+      gl_->DeleteFramebuffers(1, &source_framebuffer_);
+      source_framebuffer_ = 0;
+    }
+  }
+
+  void CopyFromTextureOrFramebuffer(bool have_source_texture,
+                                    CopyOutputResult::Format result_format,
+                                    bool scale_by_half,
+                                    bool flipped_source,
+                                    const std::string& test_name) {
+    std::unique_ptr<CopyOutputResult> result;
+
+    gfx::Rect result_selection(kRequestArea);
+    if (scale_by_half)
+      result_selection = gfx::ScaleToEnclosingRect(result_selection, 0.5f);
+
+    copy_output::RenderPassGeometry geometry;
+    // geometry.result_bounds not used by GLRendererCopier
+    geometry.sampling_bounds =
+        DrawToWindowSpace(gfx::Rect(kSourceSize), flipped_source);
+    geometry.result_selection = result_selection;
+    geometry.readback_offset =
+        DrawToWindowSpace(geometry.result_selection, flipped_source)
+            .OffsetFromOrigin();
+
+    timer_.Reset();
+    do {
+      base::RunLoop loop;
+      auto request = std::make_unique<CopyOutputRequest>(
+          result_format,
+          base::BindOnce(
+              [](std::unique_ptr<CopyOutputResult>* result,
+                 base::OnceClosure quit_closure,
+                 std::unique_ptr<CopyOutputResult> result_from_copier) {
+                *result = std::move(result_from_copier);
+                std::move(quit_closure).Run();
+              },
+              &result, loop.QuitClosure()));
+      if (scale_by_half)
+        request->SetUniformScaleRatio(2, 1);
+      const GLuint source_texture = CreateSourceTexture(flipped_source);
+      CreateAndBindSourceFramebuffer(source_texture);
+
+      copier_->CopyFromTextureOrFramebuffer(
+          std::move(request), geometry, static_cast<GLenum>(GL_RGBA),
+          have_source_texture ? source_texture : 0, kSourceSize, flipped_source,
+          gfx::ColorSpace::CreateSRGB());
+      loop.Run();
+
+      // Check that a result was produced and is of the expected rect/size.
+      ASSERT_TRUE(result);
+      ASSERT_FALSE(result->IsEmpty());
+      if (scale_by_half)
+        ASSERT_EQ(gfx::ScaleToEnclosingRect(kRequestArea, 0.5f),
+                  result->rect());
+      else
+        ASSERT_EQ(kRequestArea, result->rect());
+
+      if (result_format == CopyOutputResult::Format::RGBA_BITMAP) {
+        const SkBitmap& result_bitmap = result->AsSkBitmap();
+        ASSERT_TRUE(result_bitmap.readyToDraw());
+      } else if (result_format == CopyOutputResult::Format::I420_PLANES) {
+        const int result_width = result->rect().width();
+        const int result_height = result->rect().height();
+        const int y_width = result_width;
+        const int y_stride = y_width;
+        std::unique_ptr<uint8_t[]> y_data(
+            new uint8_t[y_stride * result_height]);
+        const int chroma_width = (result_width + 1) / 2;
+        const int u_stride = chroma_width;
+        const int v_stride = chroma_width;
+        const int chroma_height = (result_height + 1) / 2;
+        std::unique_ptr<uint8_t[]> u_data(
+            new uint8_t[u_stride * chroma_height]);
+        std::unique_ptr<uint8_t[]> v_data(
+            new uint8_t[v_stride * chroma_height]);
+
+        const bool success =
+            result->ReadI420Planes(y_data.get(), y_stride, u_data.get(),
+                                   u_stride, v_data.get(), v_stride);
+        ASSERT_TRUE(success);
+      }
+
+      DeleteSourceFramebuffer();
+      DeleteSourceTexture();
+      timer_.NextLap();
+    } while (!timer_.HasTimeLimitExpired());
+
+    perf_test::PrintResult("GLRendererCopier readback ", "", test_name,
+                           timer_.LapsPerSecond(), "runs/s", true);
+  }
+
+ private:
+  gpu::gles2::GLES2Interface* gl_ = nullptr;
+  std::unique_ptr<TextureDeleter> texture_deleter_;
+  std::unique_ptr<GLRendererCopier> copier_;
+  GLuint source_texture_ = 0;
+  GLuint source_framebuffer_ = 0;
+  base::LapTimer timer_;
+
+  DISALLOW_COPY_AND_ASSIGN(GLRendererCopierPerfTest);
+};
+
+// Fast-Path: If no transformation is necessary and no new textures need to be
+// generated, read-back directly from the currently-bound framebuffer.
+TEST_F(GLRendererCopierPerfTest, NoTransformNoNewTextures) {
+  CopyFromTextureOrFramebuffer(
+      /*have_source_texture=*/false, CopyOutputResult::Format::RGBA_BITMAP,
+      /*scale_by_half=*/false, /*flipped_source=*/false,
+      "no transformation and no new textures ");
+}
+
+// Source texture is the one attached to the framebuffer, better performance
+// than having to make a copy of the framebuffer.
+TEST_F(GLRendererCopierPerfTest, HaveTextureResultRGBABitmap) {
+  CopyFromTextureOrFramebuffer(
+      /*have_source_texture=*/true, CopyOutputResult::Format::RGBA_BITMAP,
+      /*scale_by_half=*/true, /*flipped_source=*/false,
+      "framebuffer has texture and result is RGBA_BITMAP ");
+}
+TEST_F(GLRendererCopierPerfTest, HaveTextureResultRGBATexture) {
+  CopyFromTextureOrFramebuffer(
+      /*have_source_texture=*/true, CopyOutputResult::Format::RGBA_TEXTURE,
+      /*scale_by_half=*/true, /*flipped_source=*/false,
+      "framebuffer has texture and result is RGBA_TEXTURE ");
+}
+TEST_F(GLRendererCopierPerfTest, HaveTextureResultI420Planes) {
+  CopyFromTextureOrFramebuffer(
+      /*have_source_texture=*/true, CopyOutputResult::Format::I420_PLANES,
+      /*scale_by_half=*/true, /*flipped_source=*/false,
+      "framebuffer has texture and result is I420_PLANES ");
+}
+
+// Have to make a copy of the framebuffer for the source texture.
+TEST_F(GLRendererCopierPerfTest, NoTextureResultI420Planes) {
+  CopyFromTextureOrFramebuffer(
+      /*have_source_texture=*/false, CopyOutputResult::Format::I420_PLANES,
+      /*scale_by_half=*/true, /*flipped_source=*/false,
+      "framebuffer doesn't have texture and result is I420_PLANES ");
+}
+
+// Source content is vertically flipped.
+TEST_F(GLRendererCopierPerfTest, SourceContentVerticallyFlipped) {
+  CopyFromTextureOrFramebuffer(/*have_source_texture=*/true,
+                               CopyOutputResult::Format::I420_PLANES,
+                               /*scale_by_half=*/true, /*flipped_source=*/true,
+                               "source content is vertically flipped ");
+}
+
+// Result is not scaled by half.
+TEST_F(GLRendererCopierPerfTest, ResultNotScaled) {
+  CopyFromTextureOrFramebuffer(/*have_source_texture=*/true,
+                               CopyOutputResult::Format::I420_PLANES,
+                               /*scale_by_half=*/false, /*flipped_source=*/true,
+                               "result is not scaled by half ");
+}
+
+}  // namespace viz
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index e4d58d41..9955effa 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -2388,6 +2388,8 @@
       "renderer_host/render_widget_host_view_android.h",
       "screen_orientation/screen_orientation_delegate_android.cc",
       "screen_orientation/screen_orientation_delegate_android.h",
+      "sms/sms_provider_android.cc",
+      "sms/sms_provider_android.h",
       "web_contents/web_contents_android.cc",
       "web_contents/web_contents_android.h",
       "web_contents/web_contents_view_android.cc",
@@ -2454,6 +2456,10 @@
       "serial/serial_service.cc",
       "serial/serial_service.h",
 
+      # The SMS API is stubbed out outside of Android.
+      "sms/sms_provider_desktop.cc",
+      "sms/sms_provider_desktop.h",
+
       # Most speech code is non-Android.
       "speech/audio_buffer.cc",
       "speech/audio_buffer.h",
@@ -2486,6 +2492,7 @@
       "webauth/virtual_discovery.cc",
       "webauth/virtual_discovery.h",
     ]
+
     deps += [ "//third_party/flac" ]
   }
 
diff --git a/content/browser/frame_host/render_frame_proxy_host.cc b/content/browser/frame_host/render_frame_proxy_host.cc
index 62436cd..6bbc8dc 100644
--- a/content/browser/frame_host/render_frame_proxy_host.cc
+++ b/content/browser/frame_host/render_frame_proxy_host.cc
@@ -316,6 +316,13 @@
   // in the current tab.
   DCHECK_EQ(WindowOpenDisposition::CURRENT_TAB, params.disposition);
 
+  // Augment |download_policy| for situations that were not covered on the
+  // renderer side, e.g. status not available on remote frame, etc.
+  NavigationDownloadPolicy download_policy = params.download_policy;
+  GetContentClient()->browser()->AugmentNavigationDownloadPolicy(
+      frame_tree_node_->navigator()->GetController()->GetWebContents(),
+      current_rfh, params.user_gesture, &download_policy);
+
   // TODO(alexmos, creis): Figure out whether |params.user_gesture| needs to be
   // passed in as well.
   // TODO(lfg, lukasza): Remove |extra_headers| parameter from
@@ -327,7 +334,7 @@
   frame_tree_node_->navigator()->NavigateFromFrameProxy(
       current_rfh, validated_url, params.initiator_origin, site_instance_.get(),
       params.referrer, ui::PAGE_TRANSITION_LINK,
-      params.should_replace_current_entry, params.download_policy,
+      params.should_replace_current_entry, download_policy,
       params.uses_post ? "POST" : "GET", params.resource_request_body,
       params.extra_headers, std::move(blob_url_loader_factory));
 }
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 24b3a4c..615fc70 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -63,6 +63,10 @@
     return IsIsolatedOrigin(url::Origin::Create(url));
   }
 
+  WebContentsImpl* web_contents() const {
+    return static_cast<WebContentsImpl*>(shell()->web_contents());
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(IsolatedOriginTestBase);
 };
@@ -86,10 +90,6 @@
     embedded_test_server()->StartAcceptingConnections();
   }
 
-  WebContentsImpl* web_contents() const {
-    return static_cast<WebContentsImpl*>(shell()->web_contents());
-  }
-
   void InjectAndClickLinkTo(GURL url) {
     EXPECT_TRUE(ExecuteScript(web_contents(),
                               "var link = document.createElement('a');"
@@ -102,6 +102,126 @@
   DISALLOW_COPY_AND_ASSIGN(IsolatedOriginTest);
 };
 
+class StrictOriginIsolationTest : public IsolatedOriginTestBase {
+ public:
+  StrictOriginIsolationTest() {}
+  ~StrictOriginIsolationTest() override {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    IsolatedOriginTestBase::SetUpCommandLine(command_line);
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+
+    // This is needed for this test to run properly on platforms where
+    //  --site-per-process isn't the default, such as Android.
+    IsolateAllSitesForTesting(command_line);
+    feature_list_.InitAndEnableFeature(features::kStrictOriginIsolation);
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    embedded_test_server()->StartAcceptingConnections();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(StrictOriginIsolationTest);
+};
+
+IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, SubframesAreIsolated) {
+  GURL test_url(embedded_test_server()->GetURL(
+      "foo.com",
+      "/cross_site_iframe_factory.html?"
+      "foo.com(mail.foo.com,bar.foo.com(foo.com),foo.com)"));
+  EXPECT_TRUE(NavigateToURL(shell(), test_url));
+  EXPECT_EQ(5u, shell()->web_contents()->GetAllFrames().size());
+
+  // Make sure we have three separate processes.
+  FrameTreeNode* root = web_contents()->GetFrameTree()->root();
+  RenderFrameHost* main_frame = root->current_frame_host();
+  int main_frame_id = main_frame->GetProcess()->GetID();
+  RenderFrameHost* child_frame0 = root->child_at(0)->current_frame_host();
+  int child_frame0_id = child_frame0->GetProcess()->GetID();
+  RenderFrameHost* child_frame1 = root->child_at(1)->current_frame_host();
+  int child_frame1_id = child_frame1->GetProcess()->GetID();
+  RenderFrameHost* child_frame2 = root->child_at(2)->current_frame_host();
+  int child_frame2_id = child_frame2->GetProcess()->GetID();
+  RenderFrameHost* grandchild_frame0 =
+      root->child_at(1)->child_at(0)->current_frame_host();
+  int grandchild_frame0_id = grandchild_frame0->GetProcess()->GetID();
+  EXPECT_NE(main_frame_id, child_frame0_id);
+  EXPECT_NE(main_frame_id, child_frame1_id);
+  EXPECT_EQ(main_frame_id, child_frame2_id);
+  EXPECT_EQ(main_frame_id, grandchild_frame0_id);
+
+  std::string port_string =
+      base::StringPrintf(":%u", embedded_test_server()->port());
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  EXPECT_EQ(GURL("http://foo.com" + port_string),
+            policy->GetOriginLock(main_frame_id));
+  EXPECT_EQ(GURL("http://mail.foo.com" + port_string),
+            policy->GetOriginLock(child_frame0_id));
+  EXPECT_EQ(GURL("http://bar.foo.com" + port_string),
+            policy->GetOriginLock(child_frame1_id));
+  EXPECT_EQ(GURL("http://foo.com" + port_string),
+            policy->GetOriginLock(child_frame2_id));
+  EXPECT_EQ(GURL("http://foo.com" + port_string),
+            policy->GetOriginLock(grandchild_frame0_id));
+
+  // Navigate child_frame1 to a new origin ... it should get its own process.
+  FrameTreeNode* child_frame2_node = root->child_at(2);
+  GURL foo_url(embedded_test_server()->GetURL("www.foo.com", "/title1.html"));
+  NavigateFrameToURL(child_frame2_node, foo_url);
+  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
+            child_frame2_node->current_frame_host()->GetSiteInstance());
+  // The old RenderFrameHost for subframe3 will no longer be valid, so get the
+  // new one.
+  child_frame2 = root->child_at(2)->current_frame_host();
+  EXPECT_NE(main_frame->GetProcess()->GetID(),
+            child_frame2->GetProcess()->GetID());
+  EXPECT_EQ(GURL("http://www.foo.com" + port_string),
+            policy->GetOriginLock(child_frame2->GetProcess()->GetID()));
+}
+
+IN_PROC_BROWSER_TEST_F(StrictOriginIsolationTest, MainframesAreIsolated) {
+  GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+  EXPECT_EQ(1u, web_contents()->GetAllFrames().size());
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+
+  auto foo_process_id = web_contents()->GetMainFrame()->GetProcess()->GetID();
+  SiteInstance* foo_site_instance = shell()->web_contents()->GetSiteInstance();
+  EXPECT_EQ(foo_site_instance->GetSiteURL(),
+            policy->GetOriginLock(foo_process_id));
+
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("sub.foo.com", "/title1.html")));
+  auto sub_foo_process_id =
+      shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
+  SiteInstance* sub_foo_site_instance =
+      shell()->web_contents()->GetSiteInstance();
+  EXPECT_EQ(sub_foo_site_instance->GetSiteURL(),
+            policy->GetOriginLock(sub_foo_process_id));
+
+  EXPECT_NE(foo_process_id, sub_foo_process_id);
+  EXPECT_NE(foo_site_instance->GetSiteURL(),
+            sub_foo_site_instance->GetSiteURL());
+
+  // Now verify with a renderer-initiated navigation.
+  GURL another_foo_url(
+      embedded_test_server()->GetURL("another.foo.com", "/title2.html"));
+  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), another_foo_url));
+  auto another_foo_process_id =
+      shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
+  SiteInstance* another_foo_site_instance =
+      shell()->web_contents()->GetSiteInstance();
+  EXPECT_NE(another_foo_process_id, sub_foo_process_id);
+  EXPECT_NE(another_foo_process_id, foo_process_id);
+  EXPECT_EQ(another_foo_site_instance->GetSiteURL(),
+            policy->GetOriginLock(another_foo_process_id));
+  EXPECT_NE(another_foo_site_instance, foo_site_instance);
+}
+
 // Check that navigating a main frame from an non-isolated origin to an
 // isolated origin and vice versa swaps processes and uses a new SiteInstance,
 // both for renderer-initiated and browser-initiated navigations.
diff --git a/content/browser/process_internals/process_internals_handler_impl.cc b/content/browser/process_internals/process_internals_handler_impl.cc
index cf430dd5..0e38a96 100644
--- a/content/browser/process_internals/process_internals_handler_impl.cc
+++ b/content/browser/process_internals/process_internals_handler_impl.cc
@@ -66,6 +66,8 @@
     modes.push_back("Site Per Process");
   if (SiteIsolationPolicy::AreIsolatedOriginsEnabled())
     modes.push_back("Isolate Origins");
+  if (SiteIsolationPolicy::IsStrictOriginIsolationEnabled())
+    modes.push_back("Strict Origin Isolation");
 
   // Retrieve any additional site isolation modes controlled by the embedder.
   std::vector<std::string> additional_modes =
diff --git a/content/browser/renderer_host/input/compositor_event_ack_browsertest.cc b/content/browser/renderer_host/input/compositor_event_ack_browsertest.cc
index 87c6b65..ce3d091 100644
--- a/content/browser/renderer_host/input/compositor_event_ack_browsertest.cc
+++ b/content/browser/renderer_host/input/compositor_event_ack_browsertest.cc
@@ -266,7 +266,7 @@
       blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow());
   gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = -5.f;
   GetWidgetHost()->ForwardGestureEvent(gesture_scroll_begin);
diff --git a/content/browser/renderer_host/input/fling_browsertest.cc b/content/browser/renderer_host/input/fling_browsertest.cc
index fecba2d..de467e6 100644
--- a/content/browser/renderer_host/input/fling_browsertest.cc
+++ b/content/browser/renderer_host/input/fling_browsertest.cc
@@ -151,7 +151,7 @@
         blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow());
     gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen);
     gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-        blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+        blink::WebScrollGranularity::kScrollByPrecisePixel;
     gesture_scroll_begin.data.scroll_begin.delta_x_hint = fling_velocity.x();
     gesture_scroll_begin.data.scroll_begin.delta_y_hint = fling_velocity.y();
     const gfx::PointF scroll_location_in_widget(1, 1);
diff --git a/content/browser/renderer_host/input/fling_controller_unittest.cc b/content/browser/renderer_host/input/fling_controller_unittest.cc
index a3d2d8d..22ae1a4 100644
--- a/content/browser/renderer_host/input/fling_controller_unittest.cc
+++ b/content/browser/renderer_host/input/fling_controller_unittest.cc
@@ -112,7 +112,7 @@
     scroll_update.data.scroll_update.inertial_phase =
         WebGestureEvent::kNonMomentumPhase;
     scroll_update.data.scroll_update.delta_units =
-        WebGestureEvent::kPrecisePixels;
+        blink::WebScrollGranularity::kScrollByPrecisePixel;
     GestureEventWithLatencyInfo scroll_update_with_latency(scroll_update);
 
     fling_controller_->ObserveAndMaybeConsumeGestureEvent(
diff --git a/content/browser/renderer_host/input/mouse_wheel_event_queue.cc b/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
index 3abbedab..049f46f 100644
--- a/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
+++ b/content/browser/renderer_host/input/mouse_wheel_event_queue.cc
@@ -175,7 +175,8 @@
           WebGestureEvent::kNonMomentumPhase;
     }
     if (event_sent_for_gesture_ack_->event.scroll_by_page) {
-      scroll_update.data.scroll_update.delta_units = WebGestureEvent::kPage;
+      scroll_update.data.scroll_update.delta_units =
+          blink::WebScrollGranularity::kScrollByPage;
 
       // Turn page scrolls into a *single* page scroll because
       // the magnitude the number of ticks is lost when coalescing.
@@ -188,8 +189,8 @@
     } else {
       scroll_update.data.scroll_update.delta_units =
           event_sent_for_gesture_ack_->event.has_precise_scrolling_deltas
-              ? WebGestureEvent::kPrecisePixels
-              : WebGestureEvent::kPixels;
+              ? blink::WebScrollGranularity::kScrollByPrecisePixel
+              : blink::WebScrollGranularity::kScrollByPixel;
 
       if (event_sent_for_gesture_ack_->event.rails_mode ==
           WebInputEvent::kRailsModeVertical)
diff --git a/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc b/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
index 408f7ce..d84e025 100644
--- a/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
+++ b/content/browser/renderer_host/input/mouse_wheel_event_queue_unittest.cc
@@ -277,9 +277,9 @@
   }
 
   void GestureSendingTest(bool high_precision) {
-    const WebGestureEvent::ScrollUnits scroll_units =
-        high_precision ? WebGestureEvent::kPrecisePixels
-                       : WebGestureEvent::kPixels;
+    const blink::WebScrollGranularity scroll_units =
+        high_precision ? blink::WebScrollGranularity::kScrollByPrecisePixel
+                       : blink::WebScrollGranularity::kScrollByPixel;
     SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                    kWheelScrollGlobalY, 1, 1, 0, high_precision,
                    WebMouseWheelEvent::kPhaseBegan,
@@ -313,9 +313,9 @@
   }
 
   void PhaseGestureSendingTest(bool high_precision) {
-    const WebGestureEvent::ScrollUnits scroll_units =
-        high_precision ? WebGestureEvent::kPrecisePixels
-                       : WebGestureEvent::kPixels;
+    const blink::WebScrollGranularity scroll_units =
+        high_precision ? blink::WebScrollGranularity::kScrollByPrecisePixel
+                       : blink::WebScrollGranularity::kScrollByPixel;
 
     SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                    kWheelScrollGlobalY, 1, 1, 0, high_precision,
@@ -441,8 +441,8 @@
 // scroll_end.data.scroll_end.generated_by_fling_controller.
 #if defined(CHROME_OS)
 TEST_F(MouseWheelEventQueueTest, WheelEndWithMomentumPhaseEndedInformation) {
-  const WebGestureEvent::ScrollUnits scroll_units =
-      WebGestureEvent::kPrecisePixels;
+  const blink::WebScrollGranularity scroll_units =
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                  kWheelScrollGlobalY, 1, 1, 0, true /* high_precision */,
                  WebMouseWheelEvent::kPhaseBegan,
@@ -471,7 +471,8 @@
 #endif  // defined(CHROME_OS)
 
 TEST_F(MouseWheelEventQueueTest, GestureSendingInterrupted) {
-  const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::kPixels;
+  const blink::WebScrollGranularity scroll_units =
+      blink::WebScrollGranularity::kScrollByPixel;
   SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                  kWheelScrollGlobalY, 1, 1, 0, false,
                  WebMouseWheelEvent::kPhaseBegan,
@@ -551,7 +552,8 @@
 }
 
 TEST_F(MouseWheelEventQueueTest, GestureRailScrolling) {
-  const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::kPixels;
+  const blink::WebScrollGranularity scroll_units =
+      blink::WebScrollGranularity::kScrollByPixel;
   SendMouseWheel(
       kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX, kWheelScrollGlobalY, 1,
       1, 0, false, WebMouseWheelEvent::kPhaseBegan,
@@ -600,7 +602,8 @@
 }
 
 TEST_F(MouseWheelEventQueueTest, WheelScrollLatching) {
-  const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::kPixels;
+  const blink::WebScrollGranularity scroll_units =
+      blink::WebScrollGranularity::kScrollByPixel;
   SendMouseWheel(
       kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX, kWheelScrollGlobalY, 1,
       1, 0, false, WebMouseWheelEvent::kPhaseBegan,
diff --git a/content/browser/renderer_host/input/scroll_latency_browsertest.cc b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
index b40589c..c6eb3c5 100644
--- a/content/browser/renderer_host/input/scroll_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
@@ -119,7 +119,7 @@
         SyntheticWebGestureEventBuilder::BuildScrollBegin(
             distance.x(), -distance.y(), blink::WebGestureDevice::kTouchpad, 1);
     event.data.scroll_begin.delta_hint_units =
-        blink::WebGestureEvent::ScrollUnits::kPixels;
+        blink::WebScrollGranularity::kScrollByPixel;
     GetWidgetHost()->ForwardGestureEvent(event);
 
     const uint32_t kNumWheelScrolls = 2;
@@ -139,7 +139,7 @@
               distance.x(), -distance.y(), 0,
               blink::WebGestureDevice::kTouchpad);
       event2.data.scroll_update.delta_units =
-          blink::WebGestureEvent::ScrollUnits::kPixels;
+          blink::WebScrollGranularity::kScrollByPixel;
       GetWidgetHost()->ForwardGestureEvent(event2);
 
       while (visual_state_callback_count_ <= i) {
diff --git a/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc b/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
index b24aebf..e5d62dd 100644
--- a/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
+++ b/content/browser/renderer_host/input/wheel_scroll_latching_browsertest.cc
@@ -286,8 +286,8 @@
       blink::WebInputEvent::GetStaticTimeStampForTests(),
       blink::WebGestureDevice::kTouchpad);
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      precise ? blink::WebGestureEvent::ScrollUnits::kPrecisePixels
-              : blink::WebGestureEvent::ScrollUnits::kPixels;
+      precise ? blink::WebScrollGranularity::kScrollByPrecisePixel
+              : blink::WebScrollGranularity::kScrollByPixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = -20.f;
   gesture_scroll_begin.SetPositionInWidget(gfx::PointF(x, y));
@@ -298,8 +298,8 @@
   blink::WebGestureEvent gesture_scroll_update(gesture_scroll_begin);
   gesture_scroll_update.SetType(blink::WebGestureEvent::kGestureScrollUpdate);
   gesture_scroll_update.data.scroll_update.delta_units =
-      precise ? blink::WebGestureEvent::ScrollUnits::kPrecisePixels
-              : blink::WebGestureEvent::ScrollUnits::kPixels;
+      precise ? blink::WebScrollGranularity::kScrollByPrecisePixel
+              : blink::WebScrollGranularity::kScrollByPixel;
   gesture_scroll_update.data.scroll_update.delta_x = 0.f;
   gesture_scroll_update.data.scroll_update.delta_y = -20.f;
   GetRootView()->ProcessGestureEvent(gesture_scroll_update, ui::LatencyInfo());
diff --git a/content/browser/renderer_host/overscroll_controller.cc b/content/browser/renderer_host/overscroll_controller.cc
index 6d71c9f..fd2d8bf 100644
--- a/content/browser/renderer_host/overscroll_controller.cc
+++ b/content/browser/renderer_host/overscroll_controller.cc
@@ -76,23 +76,23 @@
       if (IsGestureEventFromAutoscroll(gesture))
         return false;
 
-      blink::WebGestureEvent::ScrollUnits scrollUnits;
+      blink::WebScrollGranularity granularity;
       switch (event.GetType()) {
         case blink::WebInputEvent::kGestureScrollBegin:
-          scrollUnits = gesture.data.scroll_begin.delta_hint_units;
+          granularity = gesture.data.scroll_begin.delta_hint_units;
           break;
         case blink::WebInputEvent::kGestureScrollUpdate:
-          scrollUnits = gesture.data.scroll_update.delta_units;
+          granularity = gesture.data.scroll_update.delta_units;
           break;
         case blink::WebInputEvent::kGestureScrollEnd:
-          scrollUnits = gesture.data.scroll_end.delta_units;
+          granularity = gesture.data.scroll_end.delta_units;
           break;
         default:
-          scrollUnits = blink::WebGestureEvent::kPixels;
+          granularity = blink::WebScrollGranularity::kScrollByPixel;
           break;
       }
 
-      return scrollUnits == blink::WebGestureEvent::kPrecisePixels;
+      return granularity == blink::WebScrollGranularity::kScrollByPrecisePixel;
     }
     default:
       break;
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index dd8bdad9..a0a0926 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2584,6 +2584,8 @@
       site_isolation_mode += "spp ";
     if (SiteIsolationPolicy::AreIsolatedOriginsEnabled())
       site_isolation_mode += "io ";
+    if (SiteIsolationPolicy::IsStrictOriginIsolationEnabled())
+      site_isolation_mode += "soi ";
     if (site_isolation_mode.empty())
       site_isolation_mode = "(none)";
 
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index 53c7c53..1143fee4 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -1221,7 +1221,7 @@
   scroll_begin.data.scroll_begin.delta_x_hint = 0;
   scroll_begin.data.scroll_begin.delta_y_hint = 0;
   scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   scroll_begin.data.scroll_begin.scrollable_area_element_id = 0;
   view->ProcessGestureEvent(
       scroll_begin,
@@ -1247,7 +1247,7 @@
       scroll_end.data.scroll_end.inertial_phase =
           blink::WebGestureEvent::kUnknownMomentumPhase;
       scroll_end.data.scroll_end.delta_units =
-          blink::WebGestureEvent::kPrecisePixels;
+          blink::WebScrollGranularity::kScrollByPrecisePixel;
       break;
     default:
       NOTREACHED();
@@ -1266,7 +1266,7 @@
   scroll_end.data.scroll_end.inertial_phase =
       blink::WebGestureEvent::kUnknownMomentumPhase;
   scroll_end.data.scroll_end.delta_units =
-      blink::WebGestureEvent::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   view->ProcessGestureEvent(
       scroll_end,
       ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(scroll_end));
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 703409b..5469110 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -485,6 +485,9 @@
   if (src_origin.scheme() != dest_origin.scheme())
     return false;
 
+  if (SiteIsolationPolicy::IsStrictOriginIsolationEnabled())
+    return src_origin == dest_origin;
+
   if (!net::registry_controlled_domains::SameDomainOrHost(
           src_origin, dest_origin,
           net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
@@ -574,6 +577,9 @@
   // situation where site URL of file://localhost/ would mismatch Blink's origin
   // (which ignores the hostname in this case - see https://crbug.com/776160).
   if (!origin.host().empty() && origin.scheme() != url::kFileScheme) {
+    if (SiteIsolationPolicy::IsStrictOriginIsolationEnabled())
+      return origin.GetURL();
+
     GURL site_url(GetSiteForOrigin(origin));
 
     // Isolated origins should use the full origin as their site URL. A
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index 4cd9e0d..24287b4 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -901,6 +901,46 @@
   EXPECT_FALSE(IsSameWebSite(&context, https_bar_url, fs_bar_url));
 }
 
+TEST_F(SiteInstanceTest, StrictOriginIsolation) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kStrictOriginIsolation);
+  EXPECT_TRUE(base::FeatureList::IsEnabled(features::kStrictOriginIsolation));
+
+  GURL isolated1_foo_url("http://isolated1.foo.com");
+  GURL isolated2_foo_url("http://isolated2.foo.com");
+  TestBrowserContext browser_context;
+  IsolationContext isolation_context(&browser_context);
+
+  EXPECT_FALSE(IsSameWebSite(context(), isolated1_foo_url, isolated2_foo_url));
+  EXPECT_NE(
+      SiteInstanceImpl::GetSiteForURL(isolation_context, isolated1_foo_url),
+      SiteInstanceImpl::GetSiteForURL(isolation_context, isolated2_foo_url));
+
+  // A bunch of special cases of origins.
+  GURL secure_foo("https://foo.com");
+  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, secure_foo),
+            secure_foo);
+  GURL foo_with_port("http://foo.com:1234");
+  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, foo_with_port),
+            foo_with_port);
+  GURL local_host("http://localhost");
+  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, local_host),
+            local_host);
+  GURL ip_local_host("http://127.0.0.1");
+  EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(isolation_context, ip_local_host),
+            ip_local_host);
+
+  // The following should not get origin-specific SiteInstances, as they don't
+  // have valid hosts.
+  GURL about_url("about:flags");
+  EXPECT_NE(SiteInstanceImpl::GetSiteForURL(isolation_context, about_url),
+            about_url);
+
+  GURL file_url("file:///home/user/foo");
+  EXPECT_NE(SiteInstanceImpl::GetSiteForURL(isolation_context, file_url),
+            file_url);
+}
+
 TEST_F(SiteInstanceTest, IsolatedOrigins) {
   GURL foo_url("http://www.foo.com");
   GURL isolated_foo_url("http://isolated.foo.com");
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 40c9e790..db59da8 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -1371,7 +1371,7 @@
       blink::WebInputEvent::GetStaticTimeStampForTests(),
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f;
 
@@ -1383,7 +1383,7 @@
       blink::WebInputEvent::GetStaticTimeStampForTests(),
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_update.data.scroll_update.delta_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_update.data.scroll_update.delta_x = 0.f;
   gesture_scroll_update.data.scroll_update.delta_y = 5.f;
   gesture_scroll_update.data.scroll_update.velocity_y = 5.f;
@@ -1726,7 +1726,7 @@
       blink::WebInputEvent::GetStaticTimeStampForTests());
   gesture_scroll_begin.SetSourceDevice(blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 5.f;
   child_rwh->ForwardGestureEvent(gesture_scroll_begin);
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index ba3897f..44d14fc 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -1339,7 +1339,7 @@
         blink::WebGestureDevice::kTouchscreen);
     gesture_scroll_begin.unique_touch_event_id = 2;
     gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-        blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+        blink::WebScrollGranularity::kScrollByPrecisePixel;
     gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
     gesture_scroll_begin.data.scroll_begin.delta_y_hint = -5.f * scale_factor;
     gesture_scroll_begin.SetPositionInWidget(touch_start_point);
@@ -1351,7 +1351,7 @@
         blink::WebGestureDevice::kTouchscreen);
     gesture_scroll_update.unique_touch_event_id = 2;
     gesture_scroll_update.data.scroll_update.delta_units =
-        blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+        blink::WebScrollGranularity::kScrollByPrecisePixel;
     gesture_scroll_update.data.scroll_update.delta_x = 0.f;
     gesture_scroll_update.data.scroll_update.delta_y = -5.f * scale_factor;
     gesture_scroll_update.SetPositionInWidget(touch_start_point);
@@ -1409,7 +1409,7 @@
         blink::WebGestureDevice::kTouchscreen);
     gesture_scroll_end.unique_touch_event_id = 3;
     gesture_scroll_end.data.scroll_end.delta_units =
-        blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+        blink::WebScrollGranularity::kScrollByPrecisePixel;
     gesture_scroll_end.SetPositionInWidget(touch_move_point);
 
     InputEventAckWaiter await_scroll_end_in_child(
@@ -2253,7 +2253,7 @@
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_begin.unique_touch_event_id = 1;
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 0.f;
 #if defined(USE_AURA)
@@ -2278,7 +2278,7 @@
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_update.unique_touch_event_id = 1;
   gesture_scroll_update.data.scroll_update.delta_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_update.data.scroll_update.delta_x = 0.f;
   gesture_scroll_update.data.scroll_update.delta_y = 0.f;
 #if defined(USE_AURA)
@@ -2319,7 +2319,7 @@
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_end.unique_touch_event_id = 1;
   gesture_scroll_end.data.scroll_end.delta_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   mock_overscroll_observer->Reset();
   router->RouteGestureEvent(rwhv_root, &gesture_scroll_end,
                             ui::LatencyInfo(ui::SourceEventType::TOUCH));
diff --git a/content/browser/sms/sms_manager.cc b/content/browser/sms/sms_manager.cc
index a277ea7..ffc163d2 100644
--- a/content/browser/sms/sms_manager.cc
+++ b/content/browser/sms/sms_manager.cc
@@ -13,8 +13,7 @@
 
 namespace content {
 
-SmsManager::SmsManager()
-    : sms_provider_(std::make_unique<DefaultSmsProvider>()) {}
+SmsManager::SmsManager() : sms_provider_(SmsProvider::Create()) {}
 
 SmsManager::~SmsManager() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/content/browser/sms/sms_provider.cc b/content/browser/sms/sms_provider.cc
index ef09564..7a3f1ea 100644
--- a/content/browser/sms/sms_provider.cc
+++ b/content/browser/sms/sms_provider.cc
@@ -2,17 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <utility>
+#include <memory>
 
-#include "base/callback.h"
+#include "build/build_config.h"
 #include "content/browser/sms/sms_provider.h"
+#if defined(OS_ANDROID)
+#include "content/browser/sms/sms_provider_android.h"
+#else
+#include "content/browser/sms/sms_provider_desktop.h"
+#endif
 
 namespace content {
 
-void DefaultSmsProvider::Retrieve(base::TimeDelta timeout,
-                                  SmsCallback callback) {
-  // TODO(crbug.com/670299): implementation pending.
-  NOTIMPLEMENTED();
+// static
+std::unique_ptr<SmsProvider> SmsProvider::Create() {
+#if defined(OS_ANDROID)
+  return std::make_unique<SmsProviderAndroid>();
+#else
+  return std::make_unique<SmsProviderDesktop>();
+#endif
 }
 
 }  // namespace content
diff --git a/content/browser/sms/sms_provider.h b/content/browser/sms/sms_provider.h
index 8df482d9..ddbcebe 100644
--- a/content/browser/sms/sms_provider.h
+++ b/content/browser/sms/sms_provider.h
@@ -13,30 +13,25 @@
 
 namespace content {
 
+using SmsCallback = base::OnceCallback<void(const std::string&)>;
+
 // This class wraps the platform-specific functions and allows tests to
 // inject custom providers.
 class SmsProvider {
  public:
-  using SmsCallback = base::OnceCallback<void(const std::string&)>;
-
-  SmsProvider() {}
-  virtual ~SmsProvider() {}
+  SmsProvider() = default;
+  virtual ~SmsProvider() = default;
 
   // Listen to the next incoming SMS and call the callback when it is received.
   // On timeout, call the callback with an empty message.
   virtual void Retrieve(base::TimeDelta timeout, SmsCallback callback) = 0;
 
+  static std::unique_ptr<SmsProvider> Create();
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SmsProvider);
 };
 
-class DefaultSmsProvider : public SmsProvider {
- public:
-  DefaultSmsProvider() = default;
-  ~DefaultSmsProvider() override = default;
-  void Retrieve(base::TimeDelta timeout, SmsCallback callback) override;
-};
-
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_H_
diff --git a/content/browser/sms/sms_provider_android.cc b/content/browser/sms/sms_provider_android.cc
new file mode 100644
index 0000000..f2ee5e2
--- /dev/null
+++ b/content/browser/sms/sms_provider_android.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "content/browser/sms/sms_provider_android.h"
+
+#include "jni/SmsReceiver_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+SmsProviderAndroid::SmsProviderAndroid() {
+  // This class is constructed a single time whenever the
+  // first web page uses the SMS Retriever API to wait for
+  // SMSes.
+  JNIEnv* env = AttachCurrentThread();
+  j_sms_receiver_.Reset(
+      Java_SmsReceiver_create(env, reinterpret_cast<intptr_t>(this)));
+}
+
+SmsProviderAndroid::~SmsProviderAndroid() {
+  JNIEnv* env = AttachCurrentThread();
+  Java_SmsReceiver_destroy(env, j_sms_receiver_);
+}
+
+void SmsProviderAndroid::Retrieve(base::TimeDelta timeout,
+                                  SmsCallback callback) {
+  callback_ = std::move(callback);
+  JNIEnv* env = AttachCurrentThread();
+  Java_SmsReceiver_listen(env, j_sms_receiver_);
+}
+
+void SmsProviderAndroid::OnReceive(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    jstring message) {
+  std::string sms = ConvertJavaStringToUTF8(env, message);
+  std::move(callback_).Run(sms);
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/browser/sms/sms_provider_android.h b/content/browser/sms/sms_provider_android.h
new file mode 100644
index 0000000..7e718a7
--- /dev/null
+++ b/content/browser/sms/sms_provider_android.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 CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+#define CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
+
+#include "sms_provider.h"
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace content {
+
+class SmsProviderAndroid : public SmsProvider {
+ public:
+  SmsProviderAndroid();
+  ~SmsProviderAndroid() override;
+
+  void Retrieve(base::TimeDelta timeout, SmsCallback callback) override;
+
+ private:
+  void OnReceive(JNIEnv* env,
+                 const base::android::JavaParamRef<jobject>& obj,
+                 jstring message);
+
+  base::android::ScopedJavaGlobalRef<jobject> j_sms_receiver_;
+  SmsCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(SmsProviderAndroid);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_ANDROID_H_
\ No newline at end of file
diff --git a/content/browser/sms/sms_provider_desktop.cc b/content/browser/sms/sms_provider_desktop.cc
new file mode 100644
index 0000000..bb3a660
--- /dev/null
+++ b/content/browser/sms/sms_provider_desktop.cc
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/callback.h"
+#include "content/browser/sms/sms_provider_desktop.h"
+
+namespace content {
+
+void SmsProviderDesktop::Retrieve(base::TimeDelta timeout,
+                                  SmsCallback callback) {
+  // TODO(crbug.com/670299): implementation pending.
+  NOTIMPLEMENTED();
+}
+
+}  // namespace content
diff --git a/content/browser/sms/sms_provider_desktop.h b/content/browser/sms/sms_provider_desktop.h
new file mode 100644
index 0000000..4bba540
--- /dev/null
+++ b/content/browser/sms/sms_provider_desktop.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SMS_SMS_PROVIDER_DESKTOP_H_
+#define CONTENT_BROWSER_SMS_SMS_PROVIDER_DESKTOP_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "content/browser/sms/sms_provider.h"
+
+namespace content {
+
+class SmsProviderDesktop : public SmsProvider {
+ public:
+  SmsProviderDesktop() = default;
+  ~SmsProviderDesktop() override = default;
+  void Retrieve(base::TimeDelta timeout, SmsCallback callback) override;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SMS_SMS_PROVIDER_DESKTOP_H_
diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc
index 73f03a6..1784110 100644
--- a/content/browser/web_contents/web_contents_view_aura.cc
+++ b/content/browser/web_contents/web_contents_view_aura.cc
@@ -694,10 +694,9 @@
   if (screen_position_client)
     screen_position_client->ConvertPointFromScreen(window, &client_loc);
 
-  // |client_loc| and |screen_loc| are in the root coordinate space, for
-  // non-root RenderWidgetHosts they need to be transformed.
+  // |client_loc| is in the root coordinate space, for non-root
+  // RenderWidgetHosts it needs to be transformed.
   gfx::PointF transformed_point = client_loc;
-  gfx::PointF transformed_screen_point = screen_loc;
   if (source_rwh && web_contents_->GetRenderWidgetHostView()) {
     static_cast<RenderWidgetHostViewBase*>(
         web_contents_->GetRenderWidgetHostView())
@@ -705,17 +704,10 @@
             client_loc,
             static_cast<RenderWidgetHostViewBase*>(source_rwh->GetView()),
             &transformed_point);
-    static_cast<RenderWidgetHostViewBase*>(
-        web_contents_->GetRenderWidgetHostView())
-        ->TransformPointToCoordSpaceForView(
-            screen_loc,
-            static_cast<RenderWidgetHostViewBase*>(source_rwh->GetView()),
-            &transformed_screen_point);
   }
 
   web_contents_->DragSourceEndedAt(transformed_point.x(), transformed_point.y(),
-                                   transformed_screen_point.x(),
-                                   transformed_screen_point.y(), ops,
+                                   screen_loc.x(), screen_loc.y(), ops,
                                    source_rwh);
 
   web_contents_->SystemDragEnded(source_rwh);
@@ -1292,11 +1284,16 @@
   if (!IsValidDragTarget(target_rwh))
     return ui::DragDropTypes::DRAG_NONE;
 
+  aura::Window* root_window = GetNativeView()->GetRootWindow();
+  aura::client::ScreenPositionClient* screen_position_client =
+      aura::client::GetScreenPositionClient(root_window);
   gfx::PointF screen_pt = event.root_location_f();
+  if (screen_position_client)
+    screen_position_client->ConvertPointToScreen(root_window, &screen_pt);
+
   if (target_rwh != current_rwh_for_drag_.get()) {
     if (current_rwh_for_drag_) {
       gfx::PointF transformed_leave_point = event.location_f();
-      gfx::PointF transformed_screen_point = screen_pt;
       static_cast<RenderWidgetHostViewBase*>(
           web_contents_->GetRenderWidgetHostView())
           ->TransformPointToCoordSpaceForView(
@@ -1304,14 +1301,8 @@
               static_cast<RenderWidgetHostViewBase*>(
                   current_rwh_for_drag_->GetView()),
               &transformed_leave_point);
-      static_cast<RenderWidgetHostViewBase*>(
-          web_contents_->GetRenderWidgetHostView())
-          ->TransformPointToCoordSpaceForView(
-              screen_pt, static_cast<RenderWidgetHostViewBase*>(
-                             current_rwh_for_drag_->GetView()),
-              &transformed_screen_point);
       current_rwh_for_drag_->DragTargetDragLeave(transformed_leave_point,
-                                                 transformed_screen_point);
+                                                 screen_pt);
     }
     OnDragEntered(event);
   }
diff --git a/content/browser/web_contents/web_contents_view_aura_browsertest.cc b/content/browser/web_contents/web_contents_view_aura_browsertest.cc
index a151ac7..3e4cdcb9 100644
--- a/content/browser/web_contents/web_contents_view_aura_browsertest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_browsertest.cc
@@ -329,7 +329,7 @@
       blink::WebInputEvent::GetStaticTimeStampForTests(),
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_begin.data.scroll_begin.delta_hint_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = 0.f;
   GetRenderWidgetHost()->ForwardGestureEvent(gesture_scroll_begin);
@@ -340,7 +340,7 @@
       blink::WebInputEvent::GetStaticTimeStampForTests(),
       blink::WebGestureDevice::kTouchscreen);
   gesture_scroll_update.data.scroll_update.delta_units =
-      blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
+      blink::WebScrollGranularity::kScrollByPrecisePixel;
   gesture_scroll_update.data.scroll_update.delta_y = 0.f;
   float start_threshold = OverscrollConfig::GetThreshold(
       OverscrollConfig::Threshold::kStartTouchscreen);
diff --git a/content/browser/web_contents/web_drag_source_mac.mm b/content/browser/web_contents/web_drag_source_mac.mm
index 8c75ab5..b7716fd 100644
--- a/content/browser/web_contents/web_drag_source_mac.mm
+++ b/content/browser/web_contents/web_drag_source_mac.mm
@@ -116,9 +116,8 @@
     // If NSURL creation failed, check for a badly-escaped JavaScript URL.
     // Strip out any existing escapes and then re-escape uniformly.
     if (!url && dropData_->url.SchemeIs(url::kJavaScriptScheme)) {
-      std::string unescapedUrlString;
-      net::UnescapeBinaryURLComponent(dropData_->url.spec(),
-                                      &unescapedUrlString);
+      std::string unescapedUrlString =
+          net::UnescapeBinaryURLComponent(dropData_->url.spec());
       std::string escapedUrlString =
           net::EscapeUrlEncodedData(unescapedUrlString, false);
       url = [NSURL URLWithString:SysUTF8ToNSString(escapedUrlString)];
diff --git a/content/browser/webui/shared_resources_data_source.cc b/content/browser/webui/shared_resources_data_source.cc
index ee71a1a..f9392f3 100644
--- a/content/browser/webui/shared_resources_data_source.cc
+++ b/content/browser/webui/shared_resources_data_source.cc
@@ -133,6 +133,12 @@
 #if defined(OS_CHROMEOS)
 const std::map<int, std::string> CreateChromeosMojoResourceIdToAliasMap() {
   return std::map<int, std::string>{
+      {IDR_CELLULAR_SETUP_MOJOM_HTML,
+       "mojo/chromeos/services/cellular_setup/public/mojom/"
+       "cellular_setup.mojom.html"},
+      {IDR_CELLULAR_SETUP_MOJOM_LITE_JS,
+       "mojo/chromeos/services/cellular_setup/public/mojom/"
+       "cellular_setup.mojom-lite.js"},
       {IDR_MULTIDEVICE_DEVICE_SYNC_MOJOM_HTML,
        "mojo/chromeos/services/device_sync/public/mojom/"
        "device_sync.mojom.html"},
diff --git a/content/common/input/input_handler.mojom b/content/common/input/input_handler.mojom
index bfc92da..5e9546e 100644
--- a/content/common/input/input_handler.mojom
+++ b/content/common/input/input_handler.mojom
@@ -81,7 +81,7 @@
 struct ScrollData {
   float delta_x;
   float delta_y;
-  ScrollUnits delta_units;
+  WebScrollGranularity delta_units;
   bool target_viewport;
   InertialPhaseState inertial_phase;
   bool synthetic;
diff --git a/content/common/input_messages.h b/content/common/input_messages.h
index 83565c06..1c8d34d 100644
--- a/content/common/input_messages.h
+++ b/content/common/input_messages.h
@@ -74,8 +74,6 @@
                           blink::WebGestureDevice::kMaxValue)
 IPC_ENUM_TRAITS_MAX_VALUE(blink::WebInputEvent::DispatchType,
                           blink::WebInputEvent::DispatchType::kLastDispatchType)
-IPC_ENUM_TRAITS_MAX_VALUE(blink::WebGestureEvent::ScrollUnits,
-                          blink::WebGestureEvent::ScrollUnits::kLastScrollUnit)
 IPC_ENUM_TRAITS_MAX_VALUE(
     blink::WebGestureEvent::InertialPhaseState,
     blink::WebGestureEvent::InertialPhaseState::kLastPhase)
diff --git a/content/common/native_types.mojom b/content/common/native_types.mojom
index f87b8aa..55224b04 100644
--- a/content/common/native_types.mojom
+++ b/content/common/native_types.mojom
@@ -68,7 +68,7 @@
 enum GestureDevice;
 
 [Native]
-enum ScrollUnits;
+enum WebScrollGranularity;
 
 [Native]
 enum InertialPhaseState;
diff --git a/content/common/native_types.typemap b/content/common/native_types.typemap
index 9150ca2..9904535 100644
--- a/content/common/native_types.typemap
+++ b/content/common/native_types.typemap
@@ -79,7 +79,6 @@
   "content.mojom.DidOverscrollParams=ui::DidOverscrollParams",
   "content.mojom.PointerType=blink::WebPointerProperties::PointerType",
   "content.mojom.VisualProperties=content::VisualProperties",
-  "content.mojom.ScrollUnits=blink::WebGestureEvent::ScrollUnits",
   "content.mojom.SyntheticSmoothDrag=content::SyntheticSmoothDragGestureParams",
   "content.mojom.SyntheticSmoothScroll=content::SyntheticSmoothScrollGestureParams",
   "content.mojom.SyntheticPinch=content::SyntheticPinchGestureParams",
@@ -91,4 +90,5 @@
   "content.mojom.WebCursor=content::WebCursor",
   "content.mojom.WebPopupType=blink::WebPopupType",
   "content.mojom.WebPreferences=content::WebPreferences",
+  "content.mojom.WebScrollGranularity=blink::WebScrollGranularity",
 ]
diff --git a/content/common/navigation_params.cc b/content/common/navigation_params.cc
index ef3ec05..803e077 100644
--- a/content/common/navigation_params.cc
+++ b/content/common/navigation_params.cc
@@ -5,7 +5,6 @@
 #include "content/common/navigation_params.h"
 
 #include "base/logging.h"
-#include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/common/url_constants.h"
@@ -15,20 +14,6 @@
 
 namespace content {
 
-namespace {
-
-void LogPerPolicyApplied(NavigationDownloadType type) {
-  UMA_HISTOGRAM_ENUMERATION("Navigation.DownloadPolicy.LogPerPolicyApplied",
-                            type);
-}
-
-void LogArbitraryPolicyPerDownload(NavigationDownloadType type) {
-  UMA_HISTOGRAM_ENUMERATION(
-      "Navigation.DownloadPolicy.LogArbitraryPolicyPerDownload", type);
-}
-
-}  // namespace
-
 SourceLocation::SourceLocation() = default;
 
 SourceLocation::SourceLocation(const std::string& url,
@@ -50,67 +35,6 @@
 
 InitiatorCSPInfo::~InitiatorCSPInfo() = default;
 
-NavigationDownloadPolicy::NavigationDownloadPolicy() = default;
-NavigationDownloadPolicy::~NavigationDownloadPolicy() = default;
-NavigationDownloadPolicy::NavigationDownloadPolicy(
-    const NavigationDownloadPolicy&) = default;
-
-void NavigationDownloadPolicy::SetAllowed(NavigationDownloadType type) {
-  DCHECK(type != NavigationDownloadType::kDefaultAllow);
-  observed_types.set(static_cast<size_t>(type));
-}
-
-void NavigationDownloadPolicy::SetDisallowed(NavigationDownloadType type) {
-  DCHECK(type != NavigationDownloadType::kDefaultAllow);
-  observed_types.set(static_cast<size_t>(type));
-  disallowed_types.set(static_cast<size_t>(type));
-}
-
-bool NavigationDownloadPolicy::IsType(NavigationDownloadType type) const {
-  DCHECK(type != NavigationDownloadType::kDefaultAllow);
-  return observed_types.test(static_cast<size_t>(type));
-}
-
-ResourceInterceptPolicy NavigationDownloadPolicy::GetResourceInterceptPolicy()
-    const {
-  if (disallowed_types.test(
-          static_cast<size_t>(NavigationDownloadType::kSandboxNoGesture)) ||
-      disallowed_types.test(
-          static_cast<size_t>(NavigationDownloadType::kAdFrameNoGesture)) ||
-      disallowed_types.test(
-          static_cast<size_t>(NavigationDownloadType::kAdFrameGesture))) {
-    return ResourceInterceptPolicy::kAllowPluginOnly;
-  }
-  return disallowed_types.any() ? ResourceInterceptPolicy::kAllowNone
-                                : ResourceInterceptPolicy::kAllowAll;
-}
-
-bool NavigationDownloadPolicy::IsDownloadAllowed() const {
-  return disallowed_types.none();
-}
-
-void NavigationDownloadPolicy::RecordHistogram() const {
-  if (observed_types.none()) {
-    LogPerPolicyApplied(NavigationDownloadType::kDefaultAllow);
-    LogArbitraryPolicyPerDownload(NavigationDownloadType::kDefaultAllow);
-    return;
-  }
-
-  bool first_type_seen = false;
-  for (size_t i = 0; i < observed_types.size(); ++i) {
-    if (observed_types.test(i)) {
-      NavigationDownloadType policy = static_cast<NavigationDownloadType>(i);
-      DCHECK(policy != NavigationDownloadType::kDefaultAllow);
-      LogPerPolicyApplied(policy);
-      if (!first_type_seen) {
-        LogArbitraryPolicyPerDownload(policy);
-        first_type_seen = true;
-      }
-    }
-  }
-  DCHECK(first_type_seen);
-}
-
 CommonNavigationParams::CommonNavigationParams() = default;
 
 CommonNavigationParams::CommonNavigationParams(
diff --git a/content/common/navigation_params.h b/content/common/navigation_params.h
index 727f5cc..a02fd14 100644
--- a/content/common/navigation_params.h
+++ b/content/common/navigation_params.h
@@ -7,7 +7,6 @@
 
 #include <stdint.h>
 
-#include <bitset>
 #include <map>
 #include <string>
 
@@ -21,10 +20,10 @@
 #include "content/common/content_security_policy/csp_disposition_enum.h"
 #include "content/common/frame_message_enums.h"
 #include "content/common/service_worker/service_worker_types.h"
+#include "content/public/common/navigation_policy.h"
 #include "content/public/common/page_state.h"
 #include "content/public/common/previews_state.h"
 #include "content/public/common/referrer.h"
-#include "content/public/common/resource_intercept_policy.h"
 #include "content/public/common/was_activated_option.h"
 #include "net/url_request/redirect_info.h"
 #include "services/network/public/cpp/resource_request_body.h"
@@ -81,76 +80,6 @@
   base::Optional<CSPSource> initiator_self_source;
 };
 
-// Navigation type that affects the download decision and relevant metrics to be
-// reported at download-discovery time.
-//
-// This enum backs a histogram. Please keep enums.xml up to date with any
-// changes, and new entries should be appended at the end. Never re-arrange /
-// re-use values.
-enum class NavigationDownloadType {
-  // An entry reserved just for histogram. The client code is not expected to
-  // set or query this type in a policy.
-  kDefaultAllow = 0,
-
-  kViewSource = 1,
-  kInterstitial = 2,
-
-  // The navigation was initiated on a x-origin opener.
-  kOpenerCrossOrigin = 5,
-
-  // The navigation was initiated from or occurred in an iframe with
-  // |WebSandboxFlags::kDownloads| flag set and without user activation.
-  kSandboxNoGesture = 7,
-
-  // The navigation was initiated from or occurred in an ad frame without user
-  // activation.
-  kAdFrameNoGesture = 8,
-
-  // The navigation was initiated from or occurred in an ad frame with user
-  // activation.
-  kAdFrameGesture = 9,
-
-  kMaxValue = kAdFrameGesture
-};
-
-// Stores the navigation types that may be of interest to the download-related
-// metrics to be reported at download-discovery time. Also controls how
-// navigations behave when they turn into downloads. By default, navigation is
-// allowed to become a download.
-struct CONTENT_EXPORT NavigationDownloadPolicy {
-  NavigationDownloadPolicy();
-  ~NavigationDownloadPolicy();
-  NavigationDownloadPolicy(const NavigationDownloadPolicy&);
-
-  // Stores |type| to |observed_types|.
-  void SetAllowed(NavigationDownloadType type);
-
-  // Stores |type| to both |observed_types| and |disallowed_types|.
-  void SetDisallowed(NavigationDownloadType type);
-
-  // Returns if |observed_types| contains |type|.
-  bool IsType(NavigationDownloadType type) const;
-
-  // Get the ResourceInterceptPolicy derived from |disallowed_types|.
-  ResourceInterceptPolicy GetResourceInterceptPolicy() const;
-
-  // Returns if download is allowed based on |disallowed_types|.
-  bool IsDownloadAllowed() const;
-
-  // Record the download policy to histograms from |observed_types|.
-  void RecordHistogram() const;
-
-  // A bitset of navigation types observed that may be of interest to the
-  // download-related metrics to be reported at download-discovery time.
-  std::bitset<static_cast<size_t>(NavigationDownloadType::kMaxValue) + 1>
-      observed_types;
-
-  // A bitset of navigation types observed where if the navigation turns into
-  // a download, the download should be dropped.
-  std::bitset<static_cast<size_t>(NavigationDownloadType::kMaxValue) + 1>
-      disallowed_types;
-};
-
 // Used by all navigation IPCs.
 struct CONTENT_EXPORT CommonNavigationParams {
   CommonNavigationParams();
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 25dd5f5..172aa29f 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -52,6 +52,10 @@
 android_library("content_java") {
   deps = [
     ":content_java_resources",
+    "$google_play_services_package:google_play_services_auth_api_phone_java",
+    "$google_play_services_package:google_play_services_base_java",
+    "$google_play_services_package:google_play_services_basement_java",
+    "$google_play_services_package:google_play_services_tasks_java",
     "//base:base_java",
     "//components/download/public/common:public_java",
     "//device/bluetooth:java",
@@ -146,6 +150,7 @@
     "java/src/org/chromium/content/browser/PopupController.java",
     "java/src/org/chromium/content/browser/RenderCoordinatesImpl.java",
     "java/src/org/chromium/content/browser/ScreenOrientationProviderImpl.java",
+    "java/src/org/chromium/content/browser/SmsReceiver.java",
     "java/src/org/chromium/content/browser/SpareChildConnection.java",
     "java/src/org/chromium/content/browser/SpeechRecognitionImpl.java",
     "java/src/org/chromium/content/browser/SyntheticGestureTarget.java",
@@ -404,6 +409,7 @@
     "java/src/org/chromium/content/browser/MemoryMonitorAndroid.java",
     "java/src/org/chromium/content/browser/NfcHost.java",
     "java/src/org/chromium/content/browser/ScreenOrientationProviderImpl.java",
+    "java/src/org/chromium/content/browser/SmsReceiver.java",
     "java/src/org/chromium/content/browser/SpeechRecognitionImpl.java",
     "java/src/org/chromium/content/browser/SyntheticGestureTarget.java",
     "java/src/org/chromium/content/browser/TracingControllerAndroidImpl.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/SmsReceiver.java b/content/public/android/java/src/org/chromium/content/browser/SmsReceiver.java
new file mode 100644
index 0000000..c77d1a5
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/SmsReceiver.java
@@ -0,0 +1,131 @@
+// 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.content.browser;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.google.android.gms.auth.api.phone.SmsRetriever;
+import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
+import com.google.android.gms.common.api.CommonStatusCodes;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.android.gms.tasks.Task;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Simple proxy that provides C++ code with an access pathway to the Android
+ * SMS retriever.
+ */
+@JNINamespace("content")
+public class SmsReceiver extends BroadcastReceiver {
+    private static final String TAG = "SmsReceiver";
+    private static final boolean DEBUG = false;
+    private final long mSmsReceiverAndroid;
+    private boolean mDestroyed;
+
+    private SmsReceiver(long smsReceiverAndroid) {
+        mDestroyed = false;
+        mSmsReceiverAndroid = smsReceiverAndroid;
+
+        final Context context = ContextUtils.getApplicationContext();
+
+        // A broadcast receiver is registered upon the creation of this class
+        // which happens when the SMS Retriever API is used for the first time
+        // since chrome last restarted (which, on android, happens frequently).
+        // The broadcast receiver is fairly lightweight (e.g. it responds
+        // quickly without much computation).
+        // If this broadcast receiver becomes more heavyweight, we should make
+        // this registration expire after the SMS message is received.
+        if (DEBUG) Log.d(TAG, "Registering intent filters.");
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(SmsRetriever.SMS_RETRIEVED_ACTION);
+        context.registerReceiver(this, filter);
+    }
+
+    @CalledByNative
+    private static SmsReceiver create(long smsReceiverAndroid) {
+        if (DEBUG) Log.d(TAG, "Creating SmsReceiver.");
+        return new SmsReceiver(smsReceiverAndroid);
+    }
+
+    @CalledByNative
+    private void destroy() {
+        if (DEBUG) Log.d(TAG, "Destroying SmsReceiver.");
+        mDestroyed = true;
+        final Context context = ContextUtils.getApplicationContext();
+        context.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG) Log.d(TAG, "Received something!");
+
+        if (mDestroyed) {
+            return;
+        }
+
+        if (!SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
+            return;
+        }
+
+        if (intent.getExtras() == null) {
+            return;
+        }
+
+        final Status status;
+
+        try {
+            status = (Status) intent.getParcelableExtra(SmsRetriever.EXTRA_STATUS);
+        } catch (Throwable e) {
+            if (DEBUG) Log.d(TAG, "Error getting parceable");
+            return;
+        }
+
+        switch (status.getStatusCode()) {
+            case CommonStatusCodes.SUCCESS:
+                String message = intent.getExtras().getString(SmsRetriever.EXTRA_SMS_MESSAGE);
+                if (DEBUG) Log.d(TAG, "Got message: %s!", message);
+                nativeOnReceive(mSmsReceiverAndroid, message);
+                break;
+            case CommonStatusCodes.TIMEOUT:
+                if (DEBUG) Log.d(TAG, "Timeout");
+                break;
+        }
+    }
+
+    @CalledByNative
+    private void listen() {
+        final Context context = ContextUtils.getApplicationContext();
+
+        SmsRetrieverClient client = SmsRetriever.getClient(context);
+        Task<Void> task = client.startSmsRetriever();
+
+        if (DEBUG) {
+            task.addOnSuccessListener(new OnSuccessListener<Void>() {
+                @Override
+                public void onSuccess(Void aVoid) {
+                    if (DEBUG) Log.d(TAG, "Successfully started retriever.");
+                }
+            });
+
+            task.addOnFailureListener(new OnFailureListener() {
+                @Override
+                public void onFailure(Exception e) {
+                    if (DEBUG) Log.d(TAG, "Failed to install the retriever.");
+                }
+            });
+        }
+    }
+
+    private native static void nativeOnReceive(long nativeSmsReceiverAndroid, String sms);
+}
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 23b7c63..393155e1 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -991,4 +991,10 @@
   return base::flat_set<std::string>();
 }
 
+void ContentBrowserClient::AugmentNavigationDownloadPolicy(
+    const WebContents* web_contents,
+    const RenderFrameHost* frame_host,
+    bool user_gesture,
+    NavigationDownloadPolicy* download_policy) const {}
+
 }  // namespace content
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 1041df5..066a303e 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -33,6 +33,7 @@
 #include "content/public/browser/quota_permission_context.h"
 #include "content/public/browser/resource_request_info.h"
 #include "content/public/common/content_client.h"
+#include "content/public/common/navigation_policy.h"
 #include "content/public/common/previews_state.h"
 #include "content/public/common/resource_type.h"
 #include "content/public/common/socket_permission_request.h"
@@ -1566,6 +1567,14 @@
   // Obtains the list of MIME types that are handled by a MimeHandlerView.
   virtual base::flat_set<std::string> GetMimeHandlerViewMimeTypes(
       ResourceContext* resource_context);
+
+  // Possibly augment |download_policy| based on the status of |frame_host| as
+  // well as |user_gesture|.
+  virtual void AugmentNavigationDownloadPolicy(
+      const WebContents* web_contents,
+      const RenderFrameHost* frame_host,
+      bool user_gesture,
+      NavigationDownloadPolicy* download_policy) const;
 };
 
 }  // namespace content
diff --git a/content/public/browser/site_isolation_policy.cc b/content/public/browser/site_isolation_policy.cc
index 95595a59..edc33e5b 100644
--- a/content/public/browser/site_isolation_policy.cc
+++ b/content/public/browser/site_isolation_policy.cc
@@ -106,6 +106,19 @@
 }
 
 // static
+bool SiteIsolationPolicy::IsStrictOriginIsolationEnabled() {
+  // TODO(wjmaclean): Figure out what should happen when this feature is
+  // combined with --isolate-origins.
+  if (IsSiteIsolationDisabled())
+    return false;
+
+  // The feature needs to be checked last, because checking the feature
+  // activates the field trial and assigns the client either to a control or an
+  // experiment group - such assignment should be final.
+  return base::FeatureList::IsEnabled(features::kStrictOriginIsolation);
+}
+
+// static
 bool SiteIsolationPolicy::IsErrorPageIsolationEnabled(bool in_main_frame) {
   return GetContentClient()->browser()->ShouldIsolateErrorPage(in_main_frame);
 }
diff --git a/content/public/browser/site_isolation_policy.h b/content/public/browser/site_isolation_policy.h
index 793b9fb1..4a6dd8e 100644
--- a/content/public/browser/site_isolation_policy.h
+++ b/content/public/browser/site_isolation_policy.h
@@ -41,6 +41,10 @@
   // Returns true if isolated origins feature is enabled.
   static bool AreIsolatedOriginsEnabled();
 
+  // Returns true if strict origin isolation is enabled. Controls whether site
+  // isolation uses origins instead of scheme and eTLD+1.
+  static bool IsStrictOriginIsolationEnabled();
+
   // Returns true if error page isolation is enabled.
   static bool IsErrorPageIsolationEnabled(bool in_main_frame);
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 927a37e..f12d127 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -465,6 +465,11 @@
 const base::Feature kStaleWhileRevalidate{"StaleWhileRevalidate",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Controls whether site isolation should use origins instead of scheme and
+// eTLD+1.
+const base::Feature kStrictOriginIsolation{"StrictOriginIsolation",
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Dispatch touch events to "SyntheticGestureController" for events from
 // Devtool Protocol Input.dispatchTouchEvent to simulate touch events close to
 // real OS events.
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 174eed4..01e41fe 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -109,6 +109,7 @@
 CONTENT_EXPORT extern const base::Feature kSignedHTTPExchangePingValidity;
 CONTENT_EXPORT extern const base::Feature kSpareRendererForSitePerProcess;
 CONTENT_EXPORT extern const base::Feature kStaleWhileRevalidate;
+CONTENT_EXPORT extern const base::Feature kStrictOriginIsolation;
 CONTENT_EXPORT extern const base::Feature kSyntheticPointerActions;
 CONTENT_EXPORT extern const base::Feature kTimerThrottlingForHiddenFrames;
 CONTENT_EXPORT extern const base::Feature kTouchpadAsyncPinchEvents;
diff --git a/content/public/common/navigation_policy.cc b/content/public/common/navigation_policy.cc
index 2bfec2c1..2fea9a0 100644
--- a/content/public/common/navigation_policy.cc
+++ b/content/public/common/navigation_policy.cc
@@ -5,12 +5,27 @@
 #include "content/public/common/navigation_policy.h"
 
 #include "base/command_line.h"
+#include "base/metrics/histogram_macros.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "services/network/public/cpp/features.h"
 
 namespace content {
 
+namespace {
+
+void LogPerPolicyApplied(NavigationDownloadType type) {
+  UMA_HISTOGRAM_ENUMERATION("Navigation.DownloadPolicy.LogPerPolicyApplied",
+                            type);
+}
+
+void LogArbitraryPolicyPerDownload(NavigationDownloadType type) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "Navigation.DownloadPolicy.LogArbitraryPolicyPerDownload", type);
+}
+
+}  // namespace
+
 bool IsPerNavigationMojoInterfaceEnabled() {
   return base::FeatureList::IsEnabled(features::kPerNavigationMojoInterface);
 }
@@ -19,4 +34,67 @@
   return base::FeatureList::IsEnabled(features::kBackForwardCache);
 }
 
+NavigationDownloadPolicy::NavigationDownloadPolicy() = default;
+NavigationDownloadPolicy::~NavigationDownloadPolicy() = default;
+NavigationDownloadPolicy::NavigationDownloadPolicy(
+    const NavigationDownloadPolicy&) = default;
+
+void NavigationDownloadPolicy::SetAllowed(NavigationDownloadType type) {
+  DCHECK(type != NavigationDownloadType::kDefaultAllow);
+  observed_types.set(static_cast<size_t>(type));
+}
+
+void NavigationDownloadPolicy::SetDisallowed(NavigationDownloadType type) {
+  DCHECK(type != NavigationDownloadType::kDefaultAllow);
+  observed_types.set(static_cast<size_t>(type));
+  disallowed_types.set(static_cast<size_t>(type));
+}
+
+bool NavigationDownloadPolicy::IsType(NavigationDownloadType type) const {
+  DCHECK(type != NavigationDownloadType::kDefaultAllow);
+  return observed_types.test(static_cast<size_t>(type));
+}
+
+ResourceInterceptPolicy NavigationDownloadPolicy::GetResourceInterceptPolicy()
+    const {
+  if (disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kSandboxNoGesture)) ||
+      disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kOpenerCrossOrigin)) ||
+      disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kAdFrameNoGesture)) ||
+      disallowed_types.test(
+          static_cast<size_t>(NavigationDownloadType::kAdFrameGesture))) {
+    return ResourceInterceptPolicy::kAllowPluginOnly;
+  }
+  return disallowed_types.any() ? ResourceInterceptPolicy::kAllowNone
+                                : ResourceInterceptPolicy::kAllowAll;
+}
+
+bool NavigationDownloadPolicy::IsDownloadAllowed() const {
+  return disallowed_types.none();
+}
+
+void NavigationDownloadPolicy::RecordHistogram() const {
+  if (observed_types.none()) {
+    LogPerPolicyApplied(NavigationDownloadType::kDefaultAllow);
+    LogArbitraryPolicyPerDownload(NavigationDownloadType::kDefaultAllow);
+    return;
+  }
+
+  bool first_type_seen = false;
+  for (size_t i = 0; i < observed_types.size(); ++i) {
+    if (observed_types.test(i)) {
+      NavigationDownloadType policy = static_cast<NavigationDownloadType>(i);
+      DCHECK(policy != NavigationDownloadType::kDefaultAllow);
+      LogPerPolicyApplied(policy);
+      if (!first_type_seen) {
+        LogArbitraryPolicyPerDownload(policy);
+        first_type_seen = true;
+      }
+    }
+  }
+  DCHECK(first_type_seen);
+}
+
 }  // namespace content
diff --git a/content/public/common/navigation_policy.h b/content/public/common/navigation_policy.h
index 87fc81b..86ab5c4 100644
--- a/content/public/common/navigation_policy.h
+++ b/content/public/common/navigation_policy.h
@@ -5,7 +5,10 @@
 #ifndef CONTENT_PUBLIC_COMMON_NAVIGATION_POLICY_H_
 #define CONTENT_PUBLIC_COMMON_NAVIGATION_POLICY_H_
 
+#include <bitset>
+
 #include "content/common/content_export.h"
+#include "content/public/common/resource_intercept_policy.h"
 
 // A centralized file for base helper methods and policy decisions about
 // navigations.
@@ -15,6 +18,76 @@
 CONTENT_EXPORT bool IsPerNavigationMojoInterfaceEnabled();
 CONTENT_EXPORT bool IsBackForwardCacheEnabled();
 
+// Navigation type that affects the download decision and relevant metrics to be
+// reported at download-discovery time.
+//
+// This enum backs a histogram. Please keep enums.xml up to date with any
+// changes, and new entries should be appended at the end. Never re-arrange /
+// re-use values.
+enum class NavigationDownloadType {
+  // An entry reserved just for histogram. The client code is not expected to
+  // set or query this type in a policy.
+  kDefaultAllow = 0,
+
+  kViewSource = 1,
+  kInterstitial = 2,
+
+  // The navigation was initiated on a x-origin opener.
+  kOpenerCrossOrigin = 5,
+
+  // The navigation was initiated from or occurred in an iframe with
+  // |WebSandboxFlags::kDownloads| flag set and without user activation.
+  kSandboxNoGesture = 7,
+
+  // The navigation was initiated from or occurred in an ad frame without user
+  // activation.
+  kAdFrameNoGesture = 8,
+
+  // The navigation was initiated from or occurred in an ad frame with user
+  // activation.
+  kAdFrameGesture = 9,
+
+  kMaxValue = kAdFrameGesture
+};
+
+// Stores the navigation types that may be of interest to the download-related
+// metrics to be reported at download-discovery time. Also controls how
+// navigations behave when they turn into downloads. By default, navigation is
+// allowed to become a download.
+struct CONTENT_EXPORT NavigationDownloadPolicy {
+  NavigationDownloadPolicy();
+  ~NavigationDownloadPolicy();
+  NavigationDownloadPolicy(const NavigationDownloadPolicy&);
+
+  // Stores |type| to |observed_types|.
+  void SetAllowed(NavigationDownloadType type);
+
+  // Stores |type| to both |observed_types| and |disallowed_types|.
+  void SetDisallowed(NavigationDownloadType type);
+
+  // Returns if |observed_types| contains |type|.
+  bool IsType(NavigationDownloadType type) const;
+
+  // Get the ResourceInterceptPolicy derived from |disallowed_types|.
+  ResourceInterceptPolicy GetResourceInterceptPolicy() const;
+
+  // Returns if download is allowed based on |disallowed_types|.
+  bool IsDownloadAllowed() const;
+
+  // Record the download policy to histograms from |observed_types|.
+  void RecordHistogram() const;
+
+  // A bitset of navigation types observed that may be of interest to the
+  // download-related metrics to be reported at download-discovery time.
+  std::bitset<static_cast<size_t>(NavigationDownloadType::kMaxValue) + 1>
+      observed_types;
+
+  // A bitset of navigation types observed where if the navigation turns into
+  // a download, the download should be dropped.
+  std::bitset<static_cast<size_t>(NavigationDownloadType::kMaxValue) + 1>
+      disallowed_types;
+};
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_COMMON_NAVIGATION_POLICY_H_
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc
index e2cbf60..2dc2967 100644
--- a/content/renderer/render_frame_proxy.cc
+++ b/content/renderer/render_frame_proxy.cc
@@ -831,8 +831,8 @@
   params.blob_url_token = blob_url_token.release();
 
   // Note: For the AdFrame download policy here it only covers the case where
-  // the navigation initiator frame is ad.
-  // TODO(yaoxia): Also cover the case where the navigating frame is ad.
+  // the navigation initiator frame is ad. The download_policy may be further
+  // augmented in RenderFrameProxyHost::OnOpenURL if the navigating frame is ad.
   RenderFrameImpl::MaybeSetDownloadFramePolicy(
       is_opener_navigation, request, web_frame_->GetSecurityOrigin(),
       has_download_sandbox_flag,
diff --git a/content/shell/browser/web_test/blink_test_controller.cc b/content/shell/browser/web_test/blink_test_controller.cc
index e5a8c67..caa73be 100644
--- a/content/shell/browser/web_test/blink_test_controller.cc
+++ b/content/shell/browser/web_test/blink_test_controller.cc
@@ -19,6 +19,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/hash/md5.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/path_service.h"
diff --git a/content/shell/browser/web_test/web_test_background_fetch_delegate.cc b/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
index e8dfdb8..8bd9756 100644
--- a/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
+++ b/content/shell/browser/web_test/web_test_background_fetch_delegate.cc
@@ -17,8 +17,8 @@
 #include "components/download/public/background_service/download_params.h"
 #include "components/download/public/background_service/download_service.h"
 #include "components/download/public/background_service/features.h"
+#include "components/keyed_service/core/simple_factory_key.h"
 #include "components/keyed_service/core/simple_key_map.h"
-#include "components/keyed_service/core/test_simple_factory_key.h"
 #include "content/public/browser/background_fetch_description.h"
 #include "content/public/browser/background_fetch_response.h"
 #include "content/public/browser/browser_context.h"
diff --git a/content/shell/test_runner/event_sender.cc b/content/shell/test_runner/event_sender.cc
index 9c1f9f9e..98c4bd76 100644
--- a/content/shell/test_runner/event_sender.cc
+++ b/content/shell/test_runner/event_sender.cc
@@ -541,26 +541,26 @@
 #endif
 }
 
-bool GetScrollUnits(gin::Arguments* args, WebGestureEvent::ScrollUnits* units) {
+bool GetScrollUnits(gin::Arguments* args, blink::WebScrollGranularity* units) {
   std::string units_string;
   if (!args->PeekNext().IsEmpty()) {
     if (args->PeekNext()->IsString())
       args->GetNext(&units_string);
     if (units_string == "Page") {
-      *units = WebGestureEvent::kPage;
+      *units = blink::WebScrollGranularity::kScrollByPage;
       return true;
     } else if (units_string == "Pixels") {
-      *units = WebGestureEvent::kPixels;
+      *units = blink::WebScrollGranularity::kScrollByPixel;
       return true;
     } else if (units_string == "PrecisePixels") {
-      *units = WebGestureEvent::kPrecisePixels;
+      *units = blink::WebScrollGranularity::kScrollByPrecisePixel;
       return true;
     } else {
       args->ThrowError();
       return false;
     }
   } else {
-    *units = WebGestureEvent::kPrecisePixels;
+    *units = blink::WebScrollGranularity::kScrollByPrecisePixel;
     return true;
   }
 }
@@ -2854,7 +2854,7 @@
   begin_event.data.scroll_begin.delta_y_hint = wheel_event.delta_y;
   if (wheel_event.scroll_by_page) {
     begin_event.data.scroll_begin.delta_hint_units =
-        blink::WebGestureEvent::kPage;
+        blink::WebScrollGranularity::kScrollByPage;
     if (begin_event.data.scroll_begin.delta_x_hint) {
       begin_event.data.scroll_begin.delta_x_hint =
           begin_event.data.scroll_begin.delta_x_hint > 0 ? 1 : -1;
@@ -2866,8 +2866,8 @@
   } else {
     begin_event.data.scroll_begin.delta_hint_units =
         wheel_event.has_precise_scrolling_deltas
-            ? blink::WebGestureEvent::kPrecisePixels
-            : blink::WebGestureEvent::kPixels;
+            ? blink::WebScrollGranularity::kScrollByPrecisePixel
+            : blink::WebScrollGranularity::kScrollByPixel;
   }
 
   if (force_layout_on_events_)
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py b/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
index fa2dbf8..821f1dfc 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test_unittest.py
@@ -33,6 +33,8 @@
 path_util.AddDirToPathIfNeeded(path_util.GetChromiumSrcDir(), 'tools', 'perf')
 from chrome_telemetry_build import chromium_config
 
+GPU_CONDITIONS = ['amd', 'arm', 'broadcom', 'hisilicon', 'intel', 'imagination',
+                  'nvidia', 'qualcomm', 'vivante']
 WIN_CONDITIONS = ['xp', 'vista', 'win7', 'win8', 'win10']
 MAC_CONDITIONS = ['leopard', 'snowleopard', 'lion', 'mountainlion',
                   'mavericks', 'yosemite', 'sierra', 'highsierra', 'mojave']
@@ -185,12 +187,8 @@
   parser = expectations_parser.TaggedTestListParser(expectations)
   for exp in parser.expectations:
     _trie = trie
-    for i, l in enumerate(exp.test):
+    for l in exp.test:
       if l == '*':
-        assert i == len(exp.test)-1, (
-            ("%s:%d: '*' can only be at the "
-            "end of a test expectation's patttern")
-            % (expectations_file, exp.lineno))
         break
       assert l in _trie, (
         "%s:%d: Glob '%s' does not match with any tests in the %s test suite" %
@@ -200,6 +198,12 @@
 
 def _checkTestExpectationsForCollision(expectations, file_name):
   parser = expectations_parser.TaggedTestListParser(expectations)
+  for tag_set in parser.tag_sets:
+    if any(gpu in tag_set for gpu in GPU_CONDITIONS):
+      _map_specific_to_generic.update(
+          {t[0]: t[1] for t in
+           itertools.permutations(tag_set, 2) if (t[0] + '-').startswith(t[1])})
+      break
   tests_to_exps = defaultdict(list)
   master_conflicts_found = False
   for exp in parser.expectations:
@@ -286,7 +290,7 @@
     finally:
       temp_file.close()
 
-  def testCollisionInTestExpectationsWithGenericAndSpecificTags(self):
+  def testCollisionInTestExpectationsWithSpecifcAndGenericOsTags(self):
     test_expectations = '''# tags: [ mac win linux xp ]
     # tags: [ intel amd nvidia ]
     # tags: [ debug release ]
@@ -296,7 +300,7 @@
     with self.assertRaises(AssertionError):
       _checkTestExpectationsForCollision(test_expectations, 'test.txt')
 
-  def testNoCollisionBetweenSpecificTags(self):
+  def testNoCollisionBetweenSpecificOsTags(self):
     test_expectations = '''# tags: [ mac win linux xp win7 ]
     # tags: [ intel amd nvidia ]
     # tags: [ debug release ]
@@ -305,6 +309,25 @@
     '''
     _checkTestExpectationsForCollision(test_expectations, 'test.txt')
 
+  def testCollisionInTestExpectationsWithGpuVendorAndDeviceTags(self):
+    test_expectations = '''# tags: [ mac win linux xp ]
+    # tags: [ intel amd nvidia nvidia-0x01 ]
+    # tags: [ debug release ]
+    [ nvidia win ] a/b/c/d [ Failure ]
+    [ nvidia-0x01 xp debug ] a/b/c/d [ Skip ]
+    '''
+    with self.assertRaises(AssertionError):
+      _checkTestExpectationsForCollision(test_expectations, 'test.txt')
+
+  def testNoCollisionBetweenGpuDeviceTags(self):
+    test_expectations = '''# tags: [ mac win linux xp win7 ]
+    # tags: [ intel amd nvidia nvidia-0x01 ]
+    # tags: [ debug release ]
+    [ nvidia-0x01 win7 ] a/b/c/d [ Failure ]
+    [ nvidia-0x01 xp debug ] a/b/c/d [ Skip ]
+    '''
+    _checkTestExpectationsForCollision(test_expectations, 'test.txt')
+
   def testCollisionInTestExpectationCausesAssertion(self):
     test_expectations = '''# tags: [ mac win linux ]
     # tags: [ intel amd nvidia ]
@@ -389,14 +412,6 @@
         pixel_integration_test.PixelIntegrationTest,
         MockArgs(), pixel_test_names)
 
-  def testStarMustBeAtEndOfTestPattern(self):
-    with self.assertRaises(AssertionError) as context:
-      _testCheckTestExpectationsAreForExistingTests(
-          'a/b*/c [ Failure ]\n')
-    self.assertIn(("1: '*' can only be at the end of"
-                   " a test expectation's patttern"),
-                  str(context.exception))
-
   def testExpectationPatternNotInGeneratedTests(self):
     with self.assertRaises(AssertionError) as context:
       _testCheckTestExpectationsAreForExistingTests('a/b/d [ Failure ]')
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index ddb685b0..380c6842 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -338,8 +338,14 @@
   const base::Optional<FidoReturnCode> maybe_result =
       ConvertDeviceResponseCodeToFidoReturnCode(status);
   if (!maybe_result) {
-    FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
-                    << " from " << authenticator->GetDisplayName();
+    if (state_ == State::kWaitingForSecondTouch) {
+      OnAuthenticatorResponse(authenticator,
+                              FidoReturnCode::kAuthenticatorResponseInvalid,
+                              base::nullopt);
+    } else {
+      FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
+                      << " from " << authenticator->GetDisplayName();
+    }
     return;
   }
 
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index 37dd6b5..0083fef 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -472,6 +472,14 @@
     base::Optional<pin::EmptyResponse> response) {
   DCHECK_EQ(state_, State::kSettingPIN);
 
+  if (status != CtapDeviceResponseCode::kSuccess) {
+    state_ = State::kFinished;
+    std::move(completion_callback_)
+        .Run(FidoReturnCode::kAuthenticatorResponseInvalid, base::nullopt,
+             base::nullopt);
+    return;
+  }
+
   // Having just set the PIN, we need to immediately turn around and use it to
   // get a PIN token.
   state_ = State::kRequestWithPIN;
diff --git a/device/usb/usb_device_android.cc b/device/usb/usb_device_android.cc
index 1cc9cf6..ac5d5d0 100644
--- a/device/usb/usb_device_android.cc
+++ b/device/usb/usb_device_android.cc
@@ -205,9 +205,10 @@
   descriptor_ = *descriptor;
 
   if (usb_version() >= 0x0210) {
-    ReadWebUsbDescriptors(device_handle,
-                          base::Bind(&UsbDeviceAndroid::OnReadWebUsbDescriptors,
-                                     this, device_handle));
+    ReadWebUsbDescriptors(
+        device_handle,
+        base::BindOnce(&UsbDeviceAndroid::OnReadWebUsbDescriptors, this,
+                       device_handle));
   } else {
     device_handle->Close();
     CallRequestPermissionCallbacks(true);
diff --git a/device/usb/usb_device_win.cc b/device/usb/usb_device_win.cc
index 3707710..2f8ab07 100644
--- a/device/usb/usb_device_win.cc
+++ b/device/usb/usb_device_win.cc
@@ -136,9 +136,8 @@
   }
 
   ReadWebUsbDescriptors(
-      device_handle,
-      base::BindRepeating(&UsbDeviceWin::OnReadWebUsbDescriptors, this,
-                          base::Passed(&callback), device_handle));
+      device_handle, base::BindOnce(&UsbDeviceWin::OnReadWebUsbDescriptors,
+                                    this, std::move(callback), device_handle));
 }
 
 void UsbDeviceWin::OnReadWebUsbDescriptors(
diff --git a/device/usb/usb_service_impl.cc b/device/usb/usb_service_impl.cc
index 84ece3d..d9adcd7 100644
--- a/device/usb/usb_service_impl.cc
+++ b/device/usb/usb_service_impl.cc
@@ -202,13 +202,14 @@
 
       ReadUsbStringDescriptors(
           device_handle, std::move(string_map),
-          base::Bind(&SaveStringsAndRunContinuation, device, manufacturer,
-                     product, serial_number, barrier));
+          base::BindOnce(&SaveStringsAndRunContinuation, device, manufacturer,
+                         product, serial_number, barrier));
     }
 
     if (read_bos_descriptors) {
-      ReadWebUsbDescriptors(device_handle, base::Bind(&OnReadBosDescriptor,
-                                                      device_handle, barrier));
+      ReadWebUsbDescriptors(
+          device_handle,
+          base::BindOnce(&OnReadBosDescriptor, device_handle, barrier));
     }
   } else {
     std::move(failure_closure).Run();
diff --git a/device/usb/usb_service_linux.cc b/device/usb/usb_service_linux.cc
index 6eca4fa..4ef0e09 100644
--- a/device/usb/usb_service_linux.cc
+++ b/device/usb/usb_service_linux.cc
@@ -38,7 +38,7 @@
 
 const uint8_t kDeviceClassHub = 0x09;
 
-void OnReadDescriptors(const base::Callback<void(bool)>& callback,
+void OnReadDescriptors(base::OnceCallback<void(bool)> callback,
                        scoped_refptr<UsbDeviceHandle> device_handle,
                        const GURL& landing_page) {
   UsbDeviceLinux* device =
@@ -48,17 +48,18 @@
     device->set_webusb_landing_page(landing_page);
 
   device_handle->Close();
-  callback.Run(true /* success */);
+  std::move(callback).Run(true /* success */);
 }
 
 void OnDeviceOpenedToReadDescriptors(
-    const base::Callback<void(bool)>& callback,
+    base::OnceCallback<void(bool)> callback,
     scoped_refptr<UsbDeviceHandle> device_handle) {
   if (device_handle) {
     ReadWebUsbDescriptors(
-        device_handle, base::Bind(&OnReadDescriptors, callback, device_handle));
+        device_handle,
+        base::BindOnce(&OnReadDescriptors, std::move(callback), device_handle));
   } else {
-    callback.Run(false /* failure */);
+    std::move(callback).Run(false /* failure */);
   }
 }
 
@@ -252,8 +253,8 @@
   if (device->usb_version() >= kUsbVersion2_1) {
     device->Open(
         base::BindOnce(&OnDeviceOpenedToReadDescriptors,
-                       base::Bind(&UsbServiceLinux::DeviceReady,
-                                  weak_factory_.GetWeakPtr(), device)));
+                       base::BindOnce(&UsbServiceLinux::DeviceReady,
+                                      weak_factory_.GetWeakPtr(), device)));
   } else {
     DeviceReady(device, true /* success */);
   }
diff --git a/device/usb/webusb_descriptors.cc b/device/usb/webusb_descriptors.cc
index 8bd69a5..b229a35 100644
--- a/device/usb/webusb_descriptors.cc
+++ b/device/usb/webusb_descriptors.cc
@@ -37,72 +37,72 @@
 const int kControlTransferTimeoutMs = 2000;  // 2 seconds
 
 using ReadWebUsbDescriptorsCallback =
-    base::Callback<void(const GURL& landing_page)>;
+    base::OnceCallback<void(const GURL& landing_page)>;
 
 void OnReadLandingPage(uint8_t landing_page_id,
-                       const ReadWebUsbDescriptorsCallback& callback,
+                       ReadWebUsbDescriptorsCallback callback,
                        UsbTransferStatus status,
                        scoped_refptr<base::RefCountedBytes> buffer,
                        size_t length) {
   if (status != UsbTransferStatus::COMPLETED) {
     USB_LOG(EVENT) << "Failed to read WebUSB URL descriptor: "
                    << static_cast<int>(landing_page_id);
-    callback.Run(GURL());
+    std::move(callback).Run(GURL());
     return;
   }
 
   GURL url;
   ParseWebUsbUrlDescriptor(
       std::vector<uint8_t>(buffer->front(), buffer->front() + length), &url);
-  callback.Run(url);
+  std::move(callback).Run(url);
 }
 
 void ReadLandingPage(uint8_t vendor_code,
                      uint8_t landing_page_id,
                      scoped_refptr<UsbDeviceHandle> device_handle,
-                     const ReadWebUsbDescriptorsCallback& callback) {
+                     ReadWebUsbDescriptorsCallback callback) {
   auto buffer = base::MakeRefCounted<base::RefCountedBytes>(255);
   device_handle->ControlTransfer(
       UsbTransferDirection::INBOUND, UsbControlTransferType::VENDOR,
       UsbControlTransferRecipient::DEVICE, vendor_code, landing_page_id,
       kGetUrlRequest, buffer, kControlTransferTimeoutMs,
-      base::BindOnce(&OnReadLandingPage, landing_page_id, callback));
+      base::BindOnce(&OnReadLandingPage, landing_page_id, std::move(callback)));
 }
 
 void OnReadBosDescriptor(scoped_refptr<UsbDeviceHandle> device_handle,
-                         const ReadWebUsbDescriptorsCallback& callback,
+                         ReadWebUsbDescriptorsCallback callback,
                          UsbTransferStatus status,
                          scoped_refptr<base::RefCountedBytes> buffer,
                          size_t length) {
   if (status != UsbTransferStatus::COMPLETED) {
     USB_LOG(EVENT) << "Failed to read BOS descriptor.";
-    callback.Run(GURL());
+    std::move(callback).Run(GURL());
     return;
   }
 
   WebUsbPlatformCapabilityDescriptor descriptor;
   if (!descriptor.ParseFromBosDescriptor(
           std::vector<uint8_t>(buffer->front(), buffer->front() + length))) {
-    callback.Run(GURL());
+    std::move(callback).Run(GURL());
     return;
   }
 
   if (descriptor.landing_page_id) {
     ReadLandingPage(descriptor.vendor_code, descriptor.landing_page_id,
-                    device_handle, callback);
+                    device_handle, std::move(callback));
   } else {
-    callback.Run(GURL());
+    std::move(callback).Run(GURL());
   }
 }
 
 void OnReadBosDescriptorHeader(scoped_refptr<UsbDeviceHandle> device_handle,
-                               const ReadWebUsbDescriptorsCallback& callback,
+                               ReadWebUsbDescriptorsCallback callback,
                                UsbTransferStatus status,
                                scoped_refptr<base::RefCountedBytes> buffer,
                                size_t length) {
   if (status != UsbTransferStatus::COMPLETED || length != 5) {
     USB_LOG(EVENT) << "Failed to read BOS descriptor header.";
-    callback.Run(GURL());
+    std::move(callback).Run(GURL());
     return;
   }
 
@@ -113,7 +113,7 @@
       UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
       UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
       kBosDescriptorType << 8, 0, new_buffer, kControlTransferTimeoutMs,
-      base::BindOnce(&OnReadBosDescriptor, device_handle, callback));
+      base::BindOnce(&OnReadBosDescriptor, device_handle, std::move(callback)));
 }
 
 }  // namespace
@@ -247,13 +247,14 @@
 }
 
 void ReadWebUsbDescriptors(scoped_refptr<UsbDeviceHandle> device_handle,
-                           const ReadWebUsbDescriptorsCallback& callback) {
+                           ReadWebUsbDescriptorsCallback callback) {
   auto buffer = base::MakeRefCounted<base::RefCountedBytes>(5);
   device_handle->ControlTransfer(
       UsbTransferDirection::INBOUND, UsbControlTransferType::STANDARD,
       UsbControlTransferRecipient::DEVICE, kGetDescriptorRequest,
       kBosDescriptorType << 8, 0, buffer, kControlTransferTimeoutMs,
-      base::BindOnce(&OnReadBosDescriptorHeader, device_handle, callback));
+      base::BindOnce(&OnReadBosDescriptorHeader, device_handle,
+                     std::move(callback)));
 }
 
 }  // namespace device
diff --git a/device/usb/webusb_descriptors.h b/device/usb/webusb_descriptors.h
index 026a0f91..65513bb 100644
--- a/device/usb/webusb_descriptors.h
+++ b/device/usb/webusb_descriptors.h
@@ -33,7 +33,7 @@
 
 void ReadWebUsbDescriptors(
     scoped_refptr<UsbDeviceHandle> device_handle,
-    const base::Callback<void(const GURL& landing_page)>& callback);
+    base::OnceCallback<void(const GURL& landing_page)> callback);
 
 }  // device
 
diff --git a/device/usb/webusb_descriptors_fuzzer.cc b/device/usb/webusb_descriptors_fuzzer.cc
index c1f62e5a..1f8a652 100644
--- a/device/usb/webusb_descriptors_fuzzer.cc
+++ b/device/usb/webusb_descriptors_fuzzer.cc
@@ -27,7 +27,7 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   scoped_refptr<UsbDeviceHandle> device_handle =
       new FakeUsbDeviceHandle(data, size);
-  ReadWebUsbDescriptors(device_handle, base::Bind(&Done));
+  ReadWebUsbDescriptors(device_handle, base::BindOnce(&Done));
   return 0;
 }
 
diff --git a/device/usb/webusb_descriptors_unittest.cc b/device/usb/webusb_descriptors_unittest.cc
index fee151d5..e4e142d4 100644
--- a/device/usb/webusb_descriptors_unittest.cc
+++ b/device/usb/webusb_descriptors_unittest.cc
@@ -269,7 +269,7 @@
       .WillOnce(InvokeCallback(kExampleUrlDescriptor1,
                                sizeof(kExampleUrlDescriptor1)));
 
-  ReadWebUsbDescriptors(device_handle, base::Bind(&ExpectLandingPage));
+  ReadWebUsbDescriptors(device_handle, base::BindOnce(&ExpectLandingPage));
 }
 
 }  // namespace
diff --git a/docs/login/enterprise_enrollment.md b/docs/login/enterprise_enrollment.md
index edac77e..aeb8fb37 100644
--- a/docs/login/enterprise_enrollment.md
+++ b/docs/login/enterprise_enrollment.md
@@ -7,8 +7,11 @@
 Once you have an enterprise account, run chrome and [enroll the device](https://support.google.com/chrome/a/answer/1360534?hl=en). The shortcut combo is
 `Ctrl+Alt+E`.
 
-If you're testing on device and wish to clear enrollment state, the easiest way
-is to run `crossystem clear_tpm_owner_request=1` and then reboot. This clears
+Note, that you can only enroll device if it does not have owner (no user have
+signed in on the device, nor it was already enrolled). If device have an owner
+you would need to clear the ownership first. If you're testing on device and
+wish to clear enrollment state, the easiest way is to run
+`crossystem clear_tpm_owner_request=1` and then reboot. This clears
 TPM state which will destroy cryptohome and enrollment state. When the device
 boots next it will check and see if it needs to be force re-enrolled.
 
@@ -17,6 +20,12 @@
 privileges. You may need to log in using an incognito window if your primary
 Google account is part of an enterprise domain.
 
+Few notable policy sections in admin.google.com under
+`Device Management>Chrome>Device Settings` are `Enrollment & Access` that
+controls if device would be automatically re-enrolled after wipe and
+`Kiosk settings` that allows to configure public sessions / Kiosk mode for
+the ChromeOS device.
+
 When you're changing policies in admin.google.com, pay attention to the
 organization you are modifying. Try to only adjust your test organization to
 avoid propagating changes to other users.
\ No newline at end of file
diff --git a/extensions/browser/api/web_request/form_data_parser.cc b/extensions/browser/api/web_request/form_data_parser.cc
index 23f3ad7..79cf6564 100644
--- a/extensions/browser/api/web_request/form_data_parser.cc
+++ b/extensions/browser/api/web_request/form_data_parser.cc
@@ -395,11 +395,11 @@
     const net::UnescapeRule::Type kUnescapeRules =
         net::UnescapeRule::REPLACE_PLUS_WITH_SPACE;
 
-    std::string unescaped_name;
-    net::UnescapeBinaryURLComponent(name_, kUnescapeRules, &unescaped_name);
+    std::string unescaped_name =
+        net::UnescapeBinaryURLComponent(name_, kUnescapeRules);
     result->set_name(unescaped_name);
-    std::string unescaped_value;
-    net::UnescapeBinaryURLComponent(value_, kUnescapeRules, &unescaped_value);
+    std::string unescaped_value =
+        net::UnescapeBinaryURLComponent(value_, kUnescapeRules);
     const base::StringPiece unescaped_data(unescaped_value.data(),
                                            unescaped_value.length());
     if (base::IsStringUTF8(unescaped_data)) {
@@ -548,9 +548,7 @@
     return_value = FinishReadingPart(value_assigned ? nullptr : &value);
   }
 
-  std::string unescaped_name;
-  net::UnescapeBinaryURLComponent(name.as_string(), &unescaped_name);
-  result->set_name(unescaped_name);
+  result->set_name(net::UnescapeBinaryURLComponent(name));
   if (value_assigned) {
     // Hold filename as value.
     result->SetStringValue(value.as_string());
diff --git a/gpu/config/gpu_crash_keys.cc b/gpu/config/gpu_crash_keys.cc
index a51c27a..6f512b0 100644
--- a/gpu/config/gpu_crash_keys.cc
+++ b/gpu/config/gpu_crash_keys.cc
@@ -22,14 +22,6 @@
 #endif
 crash_reporter::CrashKeyString<4> gpu_gl_context_is_virtual(
     "gpu-gl-context-is-virtual");
-crash_reporter::CrashKeyString<20> seconds_since_last_progress_report(
-    "seconds-since-last-progress-report");
-crash_reporter::CrashKeyString<20> seconds_since_last_suspend(
-    "seconds-since-last-suspend");
-crash_reporter::CrashKeyString<20> seconds_since_last_resume(
-    "seconds-since-last-resume");
-crash_reporter::CrashKeyString<20> seconds_since_last_logging(
-    "seconds-since-last-logging");
 crash_reporter::CrashKeyString<20> available_physical_memory_in_mb(
     "available-physical-memory-in-mb");
 
diff --git a/gpu/config/gpu_crash_keys.h b/gpu/config/gpu_crash_keys.h
index e6bee5b..df1ccf1 100644
--- a/gpu/config/gpu_crash_keys.h
+++ b/gpu/config/gpu_crash_keys.h
@@ -28,11 +28,6 @@
 #endif
 extern GPU_EXPORT crash_reporter::CrashKeyString<4> gpu_gl_context_is_virtual;
 extern GPU_EXPORT crash_reporter::CrashKeyString<20>
-    seconds_since_last_progress_report;
-extern GPU_EXPORT crash_reporter::CrashKeyString<20> seconds_since_last_suspend;
-extern GPU_EXPORT crash_reporter::CrashKeyString<20> seconds_since_last_resume;
-extern GPU_EXPORT crash_reporter::CrashKeyString<20> seconds_since_last_logging;
-extern GPU_EXPORT crash_reporter::CrashKeyString<20>
     available_physical_memory_in_mb;
 
 }  // namespace crash_keys
diff --git a/gpu/ipc/common/BUILD.gn b/gpu/ipc/common/BUILD.gn
index f7a4313..f08e278 100644
--- a/gpu/ipc/common/BUILD.gn
+++ b/gpu/ipc/common/BUILD.gn
@@ -198,6 +198,7 @@
     "memory_stats.mojom",
     "surface_handle.mojom",
     "sync_token.mojom",
+    "vulkan_ycbcr_info.mojom",
   ]
 
   public_deps = [
diff --git a/gpu/ipc/common/vulkan_ycbcr_info.mojom b/gpu/ipc/common/vulkan_ycbcr_info.mojom
new file mode 100644
index 0000000..e6eadd1
--- /dev/null
+++ b/gpu/ipc/common/vulkan_ycbcr_info.mojom
@@ -0,0 +1,42 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module gpu.mojom;
+
+// Sampler Ycbcr conversion information. All of this struct parameters are
+// enums defined in the vulkan api which are passed as uint32/uint64 over ipc.
+// We use all of these values in an "opaque" way and don't consume it directly
+// in chrome.
+struct VulkanYCbCrInfo {
+  // Describes the color matrix for conversion between color models.
+  // Corresponds to vulkan type: VkSamplerYcbcrModelConversion.
+  uint32 suggested_ycbcr_model;
+
+  // Describes whether the encoded values have headroom and foot room, or
+  // whether the encoding uses the full numerical range.
+  // Corresponds to vulkan type: VkSamplerYcbcrRange.
+  uint32 suggested_ycbcr_range;
+
+  // Describes the sample location associated with downsampled chroma channels
+  // in the x dimension. It has no effect for formats in which chroma channels
+  // are the same resolution as the luma channel.
+  // Corresponds to vulkan type: VkChromaLocation.
+  uint32 suggested_xchroma_offset;
+
+  // Describes the sample location associated with downsampled chroma channels
+  // in the y dimension. It has no effect for formats in which chroma channels
+  // are not downsampled vertically.
+  // Corresponds to vulkan type: VkChromaLocation.
+  uint32 suggested_ychroma_offset;
+
+  // Implementation-defined external format identifier for use with
+  // VkExternalFormatANDROID.
+  // This property is driver specific.
+  uint64 external_format;
+
+  // Describes the capabilities of the external format when used with an image
+  // bound to memory imported from buffer.
+  // Corresponds to vulkan type: VkFormatFeatureFlags.
+  uint32 format_features;
+};
diff --git a/gpu/ipc/service/gpu_watchdog_thread.cc b/gpu/ipc/service/gpu_watchdog_thread.cc
index 4a3fc74..abec67d 100644
--- a/gpu/ipc/service/gpu_watchdog_thread.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread.cc
@@ -114,19 +114,7 @@
 }
 
 void GpuWatchdogThread::CheckArmed() {
-  last_reported_progress_timeticks_ = base::TimeTicks::Now();
-  // If the watchdog is |awaiting_acknowledge_|, reset this variable to false
-  // and post an acknowledge task now. No barrier is needed as
-  // |awaiting_acknowledge_| is only ever read from this thread.
-  if (base::subtle::NoBarrier_CompareAndSwap(&awaiting_acknowledge_, true,
-                                             false)) {
-    // Called on the monitored thread. Responds with OnAcknowledge. Cannot use
-    // the method factory. As we stop the task runner before destroying this
-    // class, the unretained reference will always outlive the task.
-    task_runner()->PostTask(FROM_HERE,
-                            base::BindOnce(&GpuWatchdogThread::OnAcknowledge,
-                                           base::Unretained(this)));
-  }
+  base::subtle::NoBarrier_Store(&awaiting_acknowledge_, false);
 }
 
 void GpuWatchdogThread::ReportProgress() {
@@ -346,6 +334,11 @@
 }
 
 void GpuWatchdogThread::OnCheckTimeout() {
+  DeliberatelyTerminateToRecoverFromHang();
+}
+
+// Use the --disable-gpu-watchdog command line switch to disable this.
+void GpuWatchdogThread::DeliberatelyTerminateToRecoverFromHang() {
   // Should not get here while the system is suspended.
   DCHECK(!suspension_counter_.HasRefs());
 
@@ -354,42 +347,15 @@
   // when a machine wakes up from sleep or hibernation, which would otherwise
   // appear to be a hang.
   if (base::Time::Now() > suspension_timeout_) {
-    armed_ = false;
-    OnCheck(true);
+    OnAcknowledge();
     return;
   }
 
   if (!base::subtle::NoBarrier_Load(&awaiting_acknowledge_)) {
-    // This should be possible only when CheckArmed() has been called but
-    // OnAcknowledge() hasn't.
-    // In this case the watched thread might need more time to finish posting
-    // OnAcknowledge task.
-
-    if (!base::FeatureList::IsEnabled(
-            features::kGpuWatchdogNoTerminationAwaitingAcknowledge)) {
-      // Continue with the termination after an additional delay.
-      task_runner()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(
-              &GpuWatchdogThread::DeliberatelyTerminateToRecoverFromHang,
-              weak_factory_.GetWeakPtr()),
-          0.5 * timeout_);
-    }
-
-    // Post a task that does nothing on the watched thread to bump its priority
-    // and make it more likely to get scheduled.
-    watched_task_runner_->PostTask(FROM_HERE, base::DoNothing());
+    OnAcknowledge();
     return;
   }
 
-  DeliberatelyTerminateToRecoverFromHang();
-}
-
-// Use the --disable-gpu-watchdog command line switch to disable this.
-void GpuWatchdogThread::DeliberatelyTerminateToRecoverFromHang() {
-  // Should not get here while the system is suspended.
-  DCHECK(!suspension_counter_.HasRefs());
-
   if (alternative_terminate_for_testing_) {
     alternative_terminate_for_testing_.Run();
     return;
@@ -403,9 +369,8 @@
   if (use_thread_cpu_time_ && (time_since_arm < timeout_)) {
     task_runner()->PostDelayedTask(
         FROM_HERE,
-        base::BindOnce(
-            &GpuWatchdogThread::DeliberatelyTerminateToRecoverFromHang,
-            weak_factory_.GetWeakPtr()),
+        base::BindOnce(&GpuWatchdogThread::OnCheckTimeout,
+                       weak_factory_.GetWeakPtr()),
         timeout_ - time_since_arm);
     return;
   }
@@ -504,8 +469,6 @@
       base::subtle::NoBarrier_Load(&awaiting_acknowledge_);
   base::debug::Alias(&awaiting_acknowledge);
 
-  base::TimeTicks before_logging_timeticks = base::TimeTicks::Now();
-
   // Don't log the message to stderr in release builds because the buffer
   // may be full.
   std::string message = base::StringPrintf(
@@ -521,28 +484,17 @@
   base::debug::Alias(&current_time);
   base::debug::Alias(&current_timeticks);
 
-  int64_t since_last_logging =
-      (current_timeticks - before_logging_timeticks).InSeconds();
-  crash_keys::seconds_since_last_logging.Set(
-      base::NumberToString(since_last_logging));
-  int64_t since_last_progress_report =
-      (current_timeticks - last_reported_progress_timeticks_).InSeconds();
-  crash_keys::seconds_since_last_progress_report.Set(
-      base::NumberToString(since_last_progress_report));
-  int64_t since_last_suspend =
-      (current_timeticks - last_suspend_timeticks_).InSeconds();
-  crash_keys::seconds_since_last_suspend.Set(
-      base::NumberToString(since_last_suspend));
-  int64_t since_last_resume =
-      (current_timeticks - last_resume_timeticks_).InSeconds();
-  crash_keys::seconds_since_last_resume.Set(
-      base::NumberToString(since_last_resume));
-
   int64_t available_physical_memory =
       base::SysInfo::AmountOfAvailablePhysicalMemory() >> 20;
   crash_keys::available_physical_memory_in_mb.Set(
       base::NumberToString(available_physical_memory));
 
+  // Check it one last time before crashing.
+  if (!base::subtle::NoBarrier_Load(&awaiting_acknowledge_)) {
+    OnAcknowledge();
+    return;
+  }
+
   // Deliberately crash the process to create a crash dump.
   *((volatile int*)0) = 0x1337;
 
@@ -591,12 +543,10 @@
 }
 
 void GpuWatchdogThread::OnSuspend() {
-  last_suspend_timeticks_ = base::TimeTicks::Now();
   power_suspend_ref_ = suspension_counter_.Take();
 }
 
 void GpuWatchdogThread::OnResume() {
-  last_resume_timeticks_ = base::TimeTicks::Now();
   power_suspend_ref_.reset();
 }
 
diff --git a/gpu/ipc/service/gpu_watchdog_thread.h b/gpu/ipc/service/gpu_watchdog_thread.h
index b9388fe..0b0155ce 100644
--- a/gpu/ipc/service/gpu_watchdog_thread.h
+++ b/gpu/ipc/service/gpu_watchdog_thread.h
@@ -187,10 +187,6 @@
   base::Time check_time_;
   base::TimeTicks check_timeticks_;
 
-  base::TimeTicks last_reported_progress_timeticks_;
-  base::TimeTicks last_suspend_timeticks_;
-  base::TimeTicks last_resume_timeticks_;
-
 #if defined(USE_X11)
   XDisplay* display_;
   gfx::AcceleratedWidget window_;
diff --git a/gpu/vulkan/android/BUILD.gn b/gpu/vulkan/android/BUILD.gn
index c9c4e59..28a39ee 100644
--- a/gpu/vulkan/android/BUILD.gn
+++ b/gpu/vulkan/android/BUILD.gn
@@ -24,6 +24,7 @@
   defines = [ "IS_VULKAN_ANDROID_IMPL" ]
 
   deps = [
+    "//gpu/ipc/common:interfaces",
     "//ui/gfx",
   ]
 
diff --git a/gpu/vulkan/android/vulkan_android_unittests.cc b/gpu/vulkan/android/vulkan_android_unittests.cc
index 44def3e6..e9bfdbd 100644
--- a/gpu/vulkan/android/vulkan_android_unittests.cc
+++ b/gpu/vulkan/android/vulkan_android_unittests.cc
@@ -51,7 +51,7 @@
   }
 
  protected:
-  std::unique_ptr<VulkanImplementationAndroid> vk_implementation_;
+  std::unique_ptr<VulkanImplementation> vk_implementation_;
   scoped_refptr<viz::VulkanInProcessContextProvider> vk_context_provider_;
   VkDevice vk_device_;
   VkPhysicalDevice vk_phy_device_;
diff --git a/gpu/vulkan/android/vulkan_implementation_android.cc b/gpu/vulkan/android/vulkan_implementation_android.cc
index 50f8cf4..1e14f87 100644
--- a/gpu/vulkan/android/vulkan_implementation_android.cc
+++ b/gpu/vulkan/android/vulkan_implementation_android.cc
@@ -8,6 +8,7 @@
 #include "base/bind_helpers.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "gpu/ipc/common/vulkan_ycbcr_info.mojom.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_instance.h"
@@ -162,7 +163,8 @@
     VkImage* vk_image,
     VkImageCreateInfo* vk_image_info,
     VkDeviceMemory* vk_device_memory,
-    VkDeviceSize* mem_allocation_size) {
+    VkDeviceSize* mem_allocation_size,
+    mojom::VulkanYCbCrInfo* ycbcr_info) {
   DCHECK(ahb_handle.is_valid());
   DCHECK(vk_image);
   DCHECK(vk_image_info);
@@ -342,6 +344,16 @@
   }
 
   *mem_allocation_size = mem_alloc_info.allocationSize;
+  if (ycbcr_info) {
+    ycbcr_info->suggested_ycbcr_model = ahb_format_props.suggestedYcbcrModel;
+    ycbcr_info->suggested_ycbcr_range = ahb_format_props.suggestedYcbcrRange;
+    ycbcr_info->suggested_xchroma_offset =
+        ahb_format_props.suggestedXChromaOffset;
+    ycbcr_info->suggested_ychroma_offset =
+        ahb_format_props.suggestedYChromaOffset;
+    ycbcr_info->external_format = ahb_format_props.externalFormat;
+    ycbcr_info->format_features = ahb_format_props.formatFeatures;
+  }
   return true;
 }
 
diff --git a/gpu/vulkan/android/vulkan_implementation_android.h b/gpu/vulkan/android/vulkan_implementation_android.h
index ee0add3a..32f66ea 100644
--- a/gpu/vulkan/android/vulkan_implementation_android.h
+++ b/gpu/vulkan/android/vulkan_implementation_android.h
@@ -59,7 +59,8 @@
       VkImage* vk_image,
       VkImageCreateInfo* vk_image_info,
       VkDeviceMemory* vk_device_memory,
-      VkDeviceSize* mem_allocation_size) override;
+      VkDeviceSize* mem_allocation_size,
+      mojom::VulkanYCbCrInfo* ycbcr_info) override;
 
  private:
   VulkanInstance vulkan_instance_;
diff --git a/gpu/vulkan/vulkan_implementation.h b/gpu/vulkan/vulkan_implementation.h
index a86b7a0..6f67249a 100644
--- a/gpu/vulkan/vulkan_implementation.h
+++ b/gpu/vulkan/vulkan_implementation.h
@@ -30,11 +30,14 @@
 }  // namespace gfx
 
 namespace gpu {
-
 class VulkanDeviceQueue;
 class VulkanSurface;
 class VulkanInstance;
 
+namespace mojom {
+class VulkanYCbCrInfo;
+}  // namespace mojom
+
 // Base class which provides functions for creating vulkan objects for different
 // platforms that use platform-specific extensions (e.g. for creation of
 // VkSurfaceKHR objects). It also provides helper/utility functions.
@@ -120,7 +123,8 @@
       VkImage* vk_image,
       VkImageCreateInfo* vk_image_info,
       VkDeviceMemory* vk_device_memory,
-      VkDeviceSize* mem_allocation_size) = 0;
+      VkDeviceSize* mem_allocation_size,
+      mojom::VulkanYCbCrInfo* ycbcr_info = nullptr) = 0;
 #endif
 
  private:
diff --git a/ios/chrome/browser/autofill/autofill_controller_js_unittest.mm b/ios/chrome/browser/autofill/autofill_controller_js_unittest.mm
index d80cc14a..a69755e 100644
--- a/ios/chrome/browser/autofill/autofill_controller_js_unittest.mm
+++ b/ios/chrome/browser/autofill/autofill_controller_js_unittest.mm
@@ -31,13 +31,10 @@
   const int option_index;
 };
 
-// TODO(crbug.com/619982): MobileSafari/iOS10 corrected
-// HTMLInputElement.maxLength with the specification
-// ( https://bugs.webkit.org/show_bug.cgi?id=154906 ). Add support for old and
-// new default maxLength value until we drop iOS 9.
 NSString* GetDefaultMaxLengthString() {
   return @"524288";
 }
+
 NSNumber* GetDefaultMaxLength() {
   return @524288;
 }
diff --git a/ios/chrome/browser/autofill/js_autofill_manager_unittest.mm b/ios/chrome/browser/autofill/js_autofill_manager_unittest.mm
index 4c43956..00dfd24 100644
--- a/ios/chrome/browser/autofill/js_autofill_manager_unittest.mm
+++ b/ios/chrome/browser/autofill/js_autofill_manager_unittest.mm
@@ -59,9 +59,6 @@
      "<TEXTAREA id='textarea-nonempty'>Go&#10;away!</TEXTAREA>"
      "<INPUT type='submit' name='reply-send' value='Send'/>";
 
-// TODO(crbug.com/619982): MobileSafari corrected HTMLInputElement.maxLength
-// with the specification ( https://bugs.webkit.org/show_bug.cgi?id=154906 ).
-// Add support for old and new default maxLength value until we dropped Xcode 7.
 NSNumber* GetDefaultMaxLength() {
   return @524288;
 }
diff --git a/ios/chrome/browser/reading_list/url_downloader.cc b/ios/chrome/browser/reading_list/url_downloader.cc
index cf22044..0b7a122be 100644
--- a/ios/chrome/browser/reading_list/url_downloader.cc
+++ b/ios/chrome/browser/reading_list/url_downloader.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/hash/md5.h"
 #include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
diff --git a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
index e161042..34e4bf6 100644
--- a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
@@ -95,6 +95,7 @@
     "//base",
     "//components/omnibox/browser",
     "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/omnibox:omnibox_util",
     "//ios/chrome/browser/ui/util",
     "//ios/third_party/material_components_ios",
diff --git a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
index ecb96d63..8fe463d0 100644
--- a/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/autocomplete_match_formatter.mm
@@ -11,6 +11,7 @@
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/suggestion_answer.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_util.h"
+#include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
 
@@ -43,6 +44,12 @@
 UIColor* DimColorIncognito() {
   return [UIColor whiteColor];
 }
+
+// Temporary convenience accessor for this flag.
+// Cleanup along with feature: crbug.com/945334.
+bool ShouldUseNewFormatting() {
+  return base::FeatureList::IsEnabled(kNewOmniboxPopupLayout);
+}
 }  // namespace
 
 @implementation AutocompleteMatchFormatter {
@@ -101,8 +108,12 @@
 
   NSAttributedString* detailAttributedText = nil;
   if (self.hasAnswer) {
-    detailAttributedText =
-        [self attributedStringWithAnswerLine:_match.answer->second_line()];
+    const SuggestionAnswer::ImageLine& detailTextLine =
+        ShouldUseNewFormatting() && !_match.answer->IsExceptedFromLineReversal()
+            ? _match.answer->first_line()
+            : _match.answer->second_line();
+    detailAttributedText = [self attributedStringWithAnswerLine:detailTextLine
+                                         useDeemphasizedStyling:YES];
   } else {
     const ACMatchClassifications* classifications =
         self.isURL ? &_match.contents_class : nullptr;
@@ -155,8 +166,12 @@
   NSAttributedString* attributedText = nil;
 
   if (self.hasAnswer) {
-    attributedText =
-        [self attributedStringWithAnswerLine:_match.answer->first_line()];
+    const SuggestionAnswer::ImageLine& textLine =
+        ShouldUseNewFormatting() && !_match.answer->IsExceptedFromLineReversal()
+            ? _match.answer->second_line()
+            : _match.answer->first_line();
+    attributedText = [self attributedStringWithAnswerLine:textLine
+                                   useDeemphasizedStyling:NO];
   } else {
     const ACMatchClassifications* textClassifications =
         !self.isURL ? &_match.contents_class : &_match.description_class;
@@ -219,27 +234,33 @@
 #pragma mark helpers
 
 // Create a string to display for an answer line.
-- (NSMutableAttributedString*)attributedStringWithAnswerLine:
-    (const SuggestionAnswer::ImageLine&)line {
+- (NSMutableAttributedString*)
+    attributedStringWithAnswerLine:(const SuggestionAnswer::ImageLine&)line
+            useDeemphasizedStyling:(BOOL)useDeemphasizedStyling {
   NSMutableAttributedString* result =
       [[NSMutableAttributedString alloc] initWithString:@""];
 
   for (const auto field : line.text_fields()) {
-    [result appendAttributedString:[self attributedStringForTextfield:&field]];
+    [result appendAttributedString:
+                [self attributedStringForTextfield:&field
+                            useDeemphasizedStyling:useDeemphasizedStyling]];
   }
 
   NSAttributedString* spacer =
       [[NSAttributedString alloc] initWithString:@"  "];
   if (line.additional_text() != nil) {
     [result appendAttributedString:spacer];
-    [result appendAttributedString:
-                [self attributedStringForTextfield:line.additional_text()]];
+    NSAttributedString* extra =
+        [self attributedStringForTextfield:line.additional_text()
+                    useDeemphasizedStyling:useDeemphasizedStyling];
+    [result appendAttributedString:extra];
   }
 
   if (line.status_text() != nil) {
     [result appendAttributedString:spacer];
     [result appendAttributedString:
-                [self attributedStringForTextfield:line.status_text()]];
+                [self attributedStringForTextfield:line.status_text()
+                            useDeemphasizedStyling:useDeemphasizedStyling]];
   }
 
   return result;
@@ -247,77 +268,10 @@
 
 // Create a string to display for a textual part ("textfield") of a suggestion
 // answer.
-- (NSAttributedString*)attributedStringForTextfield:
-    (const SuggestionAnswer::TextField*)field {
+- (NSAttributedString*)
+    attributedStringForTextfield:(const SuggestionAnswer::TextField*)field
+          useDeemphasizedStyling:(BOOL)useDeemphasizedStyling {
   const base::string16& string = field->text();
-  const int type = field->type();
-
-  NSDictionary* attributes = nil;
-
-  // Answer types, sizes and colors specified at http://goto.google.com/ais_api.
-  switch (type) {
-    case SuggestionAnswer::TOP_ALIGNED:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:12],
-        NSBaselineOffsetAttributeName : @10.0f,
-        NSForegroundColorAttributeName : [UIColor grayColor],
-      };
-      break;
-    case SuggestionAnswer::DESCRIPTION_POSITIVE:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:16],
-        NSForegroundColorAttributeName : [UIColor colorWithRed:11 / 255.0
-                                                         green:128 / 255.0
-                                                          blue:67 / 255.0
-                                                         alpha:1.0],
-      };
-      break;
-    case SuggestionAnswer::DESCRIPTION_NEGATIVE:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:16],
-        NSForegroundColorAttributeName : [UIColor colorWithRed:197 / 255.0
-                                                         green:57 / 255.0
-                                                          blue:41 / 255.0
-                                                         alpha:1.0],
-      };
-      break;
-    case SuggestionAnswer::PERSONALIZED_SUGGESTION:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:16],
-      };
-      break;
-    case SuggestionAnswer::ANSWER_TEXT_MEDIUM:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:20],
-
-        NSForegroundColorAttributeName : [UIColor grayColor],
-      };
-      break;
-    case SuggestionAnswer::ANSWER_TEXT_LARGE:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:24],
-        NSForegroundColorAttributeName : [UIColor grayColor],
-      };
-      break;
-    case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_SMALL:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:12],
-        NSForegroundColorAttributeName : [UIColor grayColor],
-      };
-      break;
-    case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_MEDIUM:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:14],
-        NSForegroundColorAttributeName : [UIColor grayColor],
-      };
-      break;
-    case SuggestionAnswer::SUGGESTION:
-    // Fall through.
-    default:
-      attributes = @{
-        NSFontAttributeName : [UIFont systemFontOfSize:16],
-      };
-  }
 
   NSString* unescapedString =
       base::SysUTF16ToNSString(net::UnescapeForHTML(string));
@@ -329,10 +283,169 @@
       [unescapedString stringByReplacingOccurrencesOfString:@"</b>"
                                                  withString:@""];
 
+  NSDictionary* attributes =
+      ShouldUseNewFormatting()
+          ? [self formattingAttributesForSuggestionStyle:field->style()
+                                  useDeemphasizedStyling:useDeemphasizedStyling]
+          : [self attributesForSuggestionType:field->type()];
+
   return [[NSAttributedString alloc] initWithString:unescapedString
                                          attributes:attributes];
 }
 
+- (NSDictionary<NSAttributedStringKey, id>*)attributesForSuggestionType:
+    (int)type {
+  DCHECK(!ShouldUseNewFormatting());
+  // Answer types, sizes and colors specified at http://goto.google.com/ais_api.
+  switch (type) {
+    case SuggestionAnswer::TOP_ALIGNED:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:12],
+        NSBaselineOffsetAttributeName : @10.0f,
+        NSForegroundColorAttributeName : [UIColor grayColor],
+      };
+    case SuggestionAnswer::DESCRIPTION_POSITIVE:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:16],
+        NSForegroundColorAttributeName : [UIColor colorWithRed:11 / 255.0
+                                                         green:128 / 255.0
+                                                          blue:67 / 255.0
+                                                         alpha:1.0],
+      };
+    case SuggestionAnswer::DESCRIPTION_NEGATIVE:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:16],
+        NSForegroundColorAttributeName : [UIColor colorWithRed:197 / 255.0
+                                                         green:57 / 255.0
+                                                          blue:41 / 255.0
+                                                         alpha:1.0],
+      };
+    case SuggestionAnswer::PERSONALIZED_SUGGESTION:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:16],
+      };
+    case SuggestionAnswer::ANSWER_TEXT_MEDIUM:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:20],
+
+        NSForegroundColorAttributeName : [UIColor grayColor],
+      };
+    case SuggestionAnswer::ANSWER_TEXT_LARGE:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:24],
+        NSForegroundColorAttributeName : [UIColor grayColor],
+      };
+    case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_SMALL:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:12],
+        NSForegroundColorAttributeName : [UIColor grayColor],
+      };
+    case SuggestionAnswer::SUGGESTION_SECONDARY_TEXT_MEDIUM:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:14],
+        NSForegroundColorAttributeName : [UIColor grayColor],
+      };
+    case SuggestionAnswer::SUGGESTION:
+      // Fall through.
+    default:
+      return @{
+        NSFontAttributeName : [UIFont systemFontOfSize:16],
+      };
+  }
+}
+
+// Return correct formatting attributes for the given style.
+// |useDeemphasizedStyling| is necessary because some styles (e.g. SUPERIOR)
+// should take their color from the surrounding line; they don't have a fixed
+// color.
+- (NSDictionary<NSAttributedStringKey, id>*)
+    formattingAttributesForSuggestionStyle:(SuggestionAnswer::TextStyle)style
+                    useDeemphasizedStyling:(BOOL)useDeemphasizedStyling {
+  DCHECK(ShouldUseNewFormatting());
+  UIFontDescriptor* defaultFontDescriptor =
+      useDeemphasizedStyling
+          ? [[UIFontDescriptor
+                preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline]
+                fontDescriptorWithSymbolicTraits:
+                    UIFontDescriptorTraitTightLeading]
+          : [UIFontDescriptor
+                preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
+  UIColor* defaultColor =
+      useDeemphasizedStyling ? UIColor.grayColor : UIColor.blackColor;
+
+  switch (style) {
+    case SuggestionAnswer::TextStyle::NORMAL:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : defaultColor,
+      };
+    case SuggestionAnswer::TextStyle::NORMAL_DIM:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : UIColor.grayColor,
+      };
+    case SuggestionAnswer::TextStyle::SECONDARY:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : UIColor.grayColor,
+      };
+    case SuggestionAnswer::TextStyle::BOLD: {
+      UIFontDescriptor* boldFontDescriptor = [defaultFontDescriptor
+          fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:boldFontDescriptor
+                                                    size:0.0],
+        NSForegroundColorAttributeName : defaultColor,
+      };
+    }
+    case SuggestionAnswer::TextStyle::POSITIVE:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : [UIColor colorWithRed:11 / 255.0
+                                                         green:128 / 255.0
+                                                          blue:67 / 255.0
+                                                         alpha:1.0],
+      };
+    case SuggestionAnswer::TextStyle::NEGATIVE:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : [UIColor colorWithRed:197 / 255.0
+                                                         green:57 / 255.0
+                                                          blue:41 / 255.0
+                                                         alpha:1.0],
+      };
+    case SuggestionAnswer::TextStyle::SUPERIOR: {
+      // Calculate a slightly smaller font. The ratio here is somewhat
+      // arbitrary. Proportions from 5/9 to 5/7 all look pretty good.
+      CGFloat ratio = 5.0 / 9.0;
+      UIFont* defaultFont = [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                  size:0];
+      UIFontDescriptor* superiorFontDescriptor = [defaultFontDescriptor
+          fontDescriptorWithSize:defaultFontDescriptor.pointSize * ratio];
+      CGFloat baselineOffset =
+          defaultFont.capHeight - defaultFont.capHeight * ratio;
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:superiorFontDescriptor
+                                                    size:0],
+        NSBaselineOffsetAttributeName :
+            [NSNumber numberWithFloat:baselineOffset],
+        NSForegroundColorAttributeName : defaultColor,
+      };
+    }
+    case SuggestionAnswer::TextStyle::NONE:
+      return @{
+        NSFontAttributeName : [UIFont fontWithDescriptor:defaultFontDescriptor
+                                                    size:0],
+        NSForegroundColorAttributeName : defaultColor,
+      };
+  }
+}
+
 // Create a formatted string given text and classifications.
 - (NSMutableAttributedString*)
     attributedStringWithString:(NSString*)text
@@ -343,8 +456,16 @@
   if (text == nil)
     return nil;
 
-  UIFont* fontRef =
-      smallFont ? [UIFont systemFontOfSize:12] : [UIFont systemFontOfSize:17];
+  UIFont* fontRef;
+  if (ShouldUseNewFormatting()) {
+    fontRef =
+        smallFont
+            ? [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
+            : [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+  } else {
+    fontRef =
+        smallFont ? [UIFont systemFontOfSize:15] : [UIFont systemFontOfSize:17];
+  }
 
   NSMutableAttributedString* styledText =
       [[NSMutableAttributedString alloc] initWithString:text];
@@ -357,8 +478,17 @@
   [styledText addAttributes:dict range:NSMakeRange(0, [text length])];
 
   if (classifications != NULL) {
-    UIFont* boldFontRef = [UIFont systemFontOfSize:fontRef.pointSize
-                                            weight:UIFontWeightMedium];
+    UIFont* boldFontRef;
+    if (ShouldUseNewFormatting()) {
+      UIFontDescriptor* fontDescriptor = fontRef.fontDescriptor;
+      UIFontDescriptor* boldFontDescriptor = [fontDescriptor
+          fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
+      boldFontRef = [UIFont fontWithDescriptor:boldFontDescriptor size:0];
+    } else {
+      UIFontWeight boldFontWeight = UIFontWeightMedium;
+      boldFontRef = [UIFont systemFontOfSize:fontRef.pointSize
+                                      weight:boldFontWeight];
+    }
 
     for (ACMatchClassifications::const_iterator i = classifications->begin();
          i != classifications->end(); ++i) {
diff --git a/ios/testing/embedded_test_server_handlers.cc b/ios/testing/embedded_test_server_handlers.cc
index 76d9c44..7c34c010 100644
--- a/ios/testing/embedded_test_server_handlers.cc
+++ b/ios/testing/embedded_test_server_handlers.cc
@@ -24,8 +24,7 @@
 std::string ExtractUlrSpecFromQuery(
     const net::test_server::HttpRequest& request) {
   GURL request_url = request.GetURL();
-  std::string spec;
-  net::UnescapeBinaryURLComponent(request_url.query(), &spec);
+  std::string spec = net::UnescapeBinaryURLComponent(request_url.query_piece());
 
   // Escape the URL spec.
   GURL url(spec);
diff --git a/ios/third_party/material_components_ios/README.chromium b/ios/third_party/material_components_ios/README.chromium
index b5d64241c..8f0080b 100644
--- a/ios/third_party/material_components_ios/README.chromium
+++ b/ios/third_party/material_components_ios/README.chromium
@@ -1,7 +1,7 @@
 Name: Material Components for iOS
 URL: https://github.com/material-components/material-components-ios
 Version: 0
-Revision: 3afe64494ecf45b4165d3bacabe9dea35cb001c5
+Revision: 17f8a299668092b0b3904db64a425556d4ebd826
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 267ae94e..a14d465 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -510,6 +510,7 @@
     "//ios/testing:ocmock_support",
     "//ios/web/common",
     "//ios/web/navigation",
+    "//ios/web/navigation:block_universal_links_buildflags",
     "//ios/web/navigation:core",
     "//ios/web/public",
     "//ios/web/public/test",
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 29cb439..80f6eed 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -19,6 +19,7 @@
 #import "ios/web/common/crw_content_view.h"
 #import "ios/web/common/crw_web_view_content_view.h"
 #include "ios/web/common/features.h"
+#include "ios/web/navigation/block_universal_links_buildflags.h"
 #import "ios/web/navigation/crw_session_controller.h"
 #import "ios/web/navigation/navigation_item_impl.h"
 #import "ios/web/navigation/navigation_manager_impl.h"
@@ -49,7 +50,6 @@
 #include "ios/web/test/test_url_constants.h"
 #import "ios/web/test/web_test_with_web_controller.h"
 #import "ios/web/test/wk_web_view_crash_utils.h"
-#include "ios/web/web_state/ui/block_universal_links_buildflags.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
 #import "ios/web/web_state/ui/web_view_js_utils.h"
diff --git a/media/base/android/media_drm_bridge_factory.cc b/media/base/android/media_drm_bridge_factory.cc
index 8dc876e0..0b9dd42 100644
--- a/media/base/android/media_drm_bridge_factory.cc
+++ b/media/base/android/media_drm_bridge_factory.cc
@@ -70,7 +70,15 @@
   // MediaDrmStorage may be lazy created in MediaDrmStorageBridge.
   storage_ = std::make_unique<MediaDrmStorageBridge>();
 
-  if (!MediaDrmBridge::IsPerOriginProvisioningSupported()) {
+  // IsPersistentLicenseTypeSupported() checks that per-origin provisioning is
+  // supported and that persistent licenses are supported. For WebView we
+  // disable persistent licenses as access to a profile is not connected, and
+  // thus persistent licenses can't be saved. As saving the origin IDs also
+  // requires data to be saved in the profile, using the persistent license
+  // check here to ensure data can be saved.
+  // TODO(crbug.com/493521). Once WebView has access to a profile to save data,
+  // switch this to calling IsPerOriginProvisioningSupported().
+  if (!MediaDrmBridge::IsPersistentLicenseTypeSupported(key_system)) {
     // Per-origin provisioning isn't supported, so proceed without
     // specifying an origin ID.
     CreateMediaDrmBridge("");
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index a01d226..7af81d1 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -177,6 +177,7 @@
     "receiver_set.h",
     "remote.h",
     "sequence_local_sync_event_watcher.h",
+    "shared_associated_remote.h",
     "shared_remote.h",
     "strong_associated_binding.h",
     "strong_binding.h",
diff --git a/mojo/public/cpp/bindings/associated_receiver.h b/mojo/public/cpp/bindings/associated_receiver.h
index 00fd11f..bdc50a5 100644
--- a/mojo/public/cpp/bindings/associated_receiver.h
+++ b/mojo/public/cpp/bindings/associated_receiver.h
@@ -106,6 +106,12 @@
   // unbinding are effectively cancelled.
   void reset() { binding_.Close(); }
 
+  // Similar to above but provides additional information to the remote endpoint
+  // about why this end is hanging up.
+  void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+    binding_.CloseWithReason(custom_reason, description);
+  }
+
   // Binds this AssociatedReceiver, connecting it to a new
   // PendingAssociatedRemote which is returned for transmission elsewhere
   // (typically to an AssociatedRemote who will consume it to start making
@@ -188,6 +194,12 @@
     binding_.AddFilter(std::move(filter));
   }
 
+  // Sends a message on the underlying message pipe and runs the current
+  // message loop until its response is received. This can be used in tests to
+  // verify that no message was sent on a message pipe in response to some
+  // stimulus.
+  void FlushForTesting() { binding_.FlushForTesting(); }
+
  private:
   // TODO(https://crbug.com/875030): Move AssociatedBinding details into this
   // class.
diff --git a/mojo/public/cpp/bindings/associated_remote.h b/mojo/public/cpp/bindings/associated_remote.h
index c324c0f9..8ff75188 100644
--- a/mojo/public/cpp/bindings/associated_remote.h
+++ b/mojo/public/cpp/bindings/associated_remote.h
@@ -123,6 +123,14 @@
       internal_state_.set_connection_error_handler(std::move(handler));
   }
 
+  // Similar to above but the handler receives additional metadata if provided
+  // by the receiving endpoint when closing itself.
+  void set_disconnect_with_reason_handler(
+      ConnectionErrorWithReasonCallback handler) {
+    internal_state_.set_connection_error_with_reason_handler(
+        std::move(handler));
+  }
+
   // Resets this AssociatedRemote to an unbound state. To reset the
   // AssociatedRemote and recover an PendingAssociatedRemote that can be bound
   // again later, use |Unbind()| instead.
@@ -220,6 +228,12 @@
                                               info.version());
   }
 
+  // Sends a message on the underlying message pipe and runs the current
+  // message loop until its response is received. This can be used in tests to
+  // verify that no message was sent on a message pipe in response to some
+  // stimulus.
+  void FlushForTesting() { internal_state_.FlushForTesting(); }
+
   internal::AssociatedInterfacePtrState<Interface>* internal_state() {
     return &internal_state_;
   }
diff --git a/mojo/public/cpp/bindings/pending_associated_receiver.h b/mojo/public/cpp/bindings/pending_associated_receiver.h
index f808e364..4e5d4d97 100644
--- a/mojo/public/cpp/bindings/pending_associated_receiver.h
+++ b/mojo/public/cpp/bindings/pending_associated_receiver.h
@@ -56,6 +56,15 @@
     handle_ = std::move(handle);
   }
 
+  // Hangs up this endpoint, invalidating the PendingAssociatedReceiver.
+  void reset() { handle_.reset(); }
+
+  // Similar to above but provides additional metadata in case the remote
+  // endpoint wants details about why this endpoint hung up.
+  void ResetWithReason(uint32_t custom_reason, const std::string& description) {
+    handle_.ResetWithReason(custom_reason, description);
+  }
+
  private:
   ScopedInterfaceEndpointHandle handle_;
 
diff --git a/mojo/public/cpp/bindings/pending_associated_remote.h b/mojo/public/cpp/bindings/pending_associated_remote.h
index e1043384..dc2087c 100644
--- a/mojo/public/cpp/bindings/pending_associated_remote.h
+++ b/mojo/public/cpp/bindings/pending_associated_remote.h
@@ -45,6 +45,8 @@
   bool is_valid() const { return handle_.is_valid(); }
   explicit operator bool() const { return is_valid(); }
 
+  void reset() { handle_.reset(); }
+
   // Temporary helper for transitioning away from old bindings types. This is
   // intentionally an implicit conversion.
   operator AssociatedInterfacePtrInfo<Interface>() {
diff --git a/mojo/public/cpp/bindings/shared_associated_remote.h b/mojo/public/cpp/bindings/shared_associated_remote.h
new file mode 100644
index 0000000..20aa48b
--- /dev/null
+++ b/mojo/public/cpp/bindings/shared_associated_remote.h
@@ -0,0 +1,64 @@
+// 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 MOJO_PUBLIC_CPP_BINDINGS_SHARED_ASSOCIATED_REMOTE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SHARED_ASSOCIATED_REMOTE_H_
+
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/shared_remote.h"
+
+namespace mojo {
+
+// SharedAssociatedRemote wraps a non-thread-safe AssociatedRemote and proxies
+// messages to it. Unlike normal AssociatedRemote objects,
+// SharedAssociatedRemote is copyable and usable from any thread, but has some
+// additional overhead and latency in message transmission as a trade-off.
+//
+// Async calls are posted to the sequence that the underlying AssociatedRemote
+// is bound to, and responses are posted back to the calling sequence. Sync
+// calls are dispatched directly if the call is made on the sequence that the
+// wrapped AssociatedRemote is bound to, or posted otherwise. It's important to
+// be aware that sync calls block both the calling sequence and the bound
+// AssociatedRemote's sequence. That means that you cannot make sync calls
+// through a SharedAssociatedRemote if the underlying AssociatedRemote is bound
+// to a sequence that cannot block, like the IPC thread.
+template <typename Interface>
+class SharedAssociatedRemote {
+ public:
+  SharedAssociatedRemote() = default;
+  explicit SharedAssociatedRemote(
+      PendingAssociatedRemote<Interface> pending_remote)
+      : remote_(pending_remote.is_valid()
+                    ? SharedRemoteBase<AssociatedRemote<Interface>>::Create(
+                          std::move(pending_remote))
+                    : nullptr) {}
+  SharedAssociatedRemote(
+      PendingAssociatedRemote<Interface> pending_remote,
+      scoped_refptr<base::SequencedTaskRunner> bind_task_runner)
+      : remote_(pending_remote.is_valid()
+                    ? SharedRemoteBase<AssociatedRemote<Interface>>::Create(
+                          std::move(pending_remote),
+                          std::move(bind_task_runner))
+                    : nullptr) {}
+
+  bool is_bound() const { return remote_ != nullptr; }
+  explicit operator bool() const { return is_bound(); }
+
+  Interface* get() const { return remote_->get(); }
+  Interface* operator->() const { return get(); }
+  Interface& operator*() const { return *get(); }
+
+  // Clears this SharedAssociatedRemote. Note that this does *not* necessarily
+  // close the remote's endpoint as other SharedAssociatedRemote instances may
+  // reference the same underlying endpoint.
+  void reset() { remote_.reset(); }
+
+ private:
+  scoped_refptr<SharedRemoteBase<AssociatedRemote<Interface>>> remote_;
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_SHARED_ASSOCIATED_REMOTE_H_
diff --git a/mojo/public/cpp/bindings/shared_remote.h b/mojo/public/cpp/bindings/shared_remote.h
index 104192eb..607c324 100644
--- a/mojo/public/cpp/bindings/shared_remote.h
+++ b/mojo/public/cpp/bindings/shared_remote.h
@@ -38,11 +38,12 @@
       : forwarder_(std::move(forwarder)) {}
 
   // Creates a SharedRemoteBase wrapping an underlying non-thread-safe
-  // RemoteType which is bound to the calling sequence. All messages sent
+  // PendingType which is bound to the calling sequence. All messages sent
   // via this thread-safe proxy will internally be sent by first posting to this
   // (the calling) sequence's TaskRunner.
-  static scoped_refptr<SharedRemoteBase> Create(RemoteType remote) {
-    scoped_refptr<RemoteWrapper> wrapper = new RemoteWrapper(std::move(remote));
+  static scoped_refptr<SharedRemoteBase> Create(PendingType pending_remote) {
+    scoped_refptr<RemoteWrapper> wrapper =
+        new RemoteWrapper(RemoteType(std::move(pending_remote)));
     return new SharedRemoteBase(wrapper->CreateForwarder());
   }
 
@@ -83,7 +84,7 @@
     explicit RemoteWrapper(scoped_refptr<base::SequencedTaskRunner> task_runner)
         : task_runner_(std::move(task_runner)) {}
 
-    void BindOnTaskRunner(PendingRemote<InterfaceType> remote) {
+    void BindOnTaskRunner(PendingType remote) {
       // TODO(https://crbug.com/682334): At the moment we don't have a group
       // controller available. That means the user won't be able to pass
       // associated endpoints on this interface (at least not immediately). In
@@ -173,8 +174,10 @@
  public:
   SharedRemote() = default;
   explicit SharedRemote(PendingRemote<Interface> pending_remote)
-      : SharedRemote(std::move(pending_remote),
-                     base::SequencedTaskRunnerHandle::Get()) {}
+      : remote_(pending_remote.is_valid()
+                    ? SharedRemoteBase<Remote<Interface>>::Create(
+                          std::move(pending_remote))
+                    : nullptr) {}
   SharedRemote(PendingRemote<Interface> pending_remote,
                scoped_refptr<base::SequencedTaskRunner> bind_task_runner)
       : remote_(pending_remote.is_valid()
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
index 5c0af26a..c276e9e0 100644
--- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -19,14 +19,15 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
-#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
-#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h"
-#include "mojo/public/cpp/bindings/associated_interface_request.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/lib/multiplex_router.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/shared_associated_remote.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
 #include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -39,14 +40,14 @@
 
 class IntegerSenderImpl : public IntegerSender {
  public:
-  explicit IntegerSenderImpl(AssociatedInterfaceRequest<IntegerSender> request)
-      : binding_(this, std::move(request)) {}
+  explicit IntegerSenderImpl(PendingAssociatedReceiver<IntegerSender> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
-  ~IntegerSenderImpl() override {}
+  ~IntegerSenderImpl() override = default;
 
   void set_notify_send_method_called(
-      const base::Callback<void(int32_t)>& callback) {
-    notify_send_method_called_ = callback;
+      base::RepeatingCallback<void(int32_t)> callback) {
+    notify_send_method_called_ = std::move(callback);
   }
 
   void Echo(int32_t value, EchoCallback callback) override {
@@ -54,64 +55,57 @@
   }
   void Send(int32_t value) override { notify_send_method_called_.Run(value); }
 
-  AssociatedBinding<IntegerSender>* binding() { return &binding_; }
-
-  void set_connection_error_handler(const base::Closure& handler) {
-    binding_.set_connection_error_handler(handler);
-  }
+  AssociatedReceiver<IntegerSender>* receiver() { return &receiver_; }
 
  private:
-  AssociatedBinding<IntegerSender> binding_;
-  base::Callback<void(int32_t)> notify_send_method_called_;
+  AssociatedReceiver<IntegerSender> receiver_;
+  base::RepeatingCallback<void(int32_t)> notify_send_method_called_;
 };
 
 class IntegerSenderConnectionImpl : public IntegerSenderConnection {
  public:
   explicit IntegerSenderConnectionImpl(
-      InterfaceRequest<IntegerSenderConnection> request)
-      : binding_(this, std::move(request)) {}
+      PendingReceiver<IntegerSenderConnection> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
-  ~IntegerSenderConnectionImpl() override {}
+  ~IntegerSenderConnectionImpl() override = default;
 
-  void GetSender(AssociatedInterfaceRequest<IntegerSender> sender) override {
-    IntegerSenderImpl* sender_impl = new IntegerSenderImpl(std::move(sender));
-    sender_impl->set_connection_error_handler(
-        base::Bind(&DeleteSender, sender_impl));
+  void GetSender(PendingAssociatedReceiver<IntegerSender> receiver) override {
+    IntegerSenderImpl* sender_impl = new IntegerSenderImpl(std::move(receiver));
+    sender_impl->receiver()->set_disconnect_handler(
+        base::BindOnce(&DeleteSender, sender_impl));
   }
 
   void AsyncGetSender(AsyncGetSenderCallback callback) override {
-    IntegerSenderAssociatedPtrInfo ptr_info;
-    auto request = MakeRequest(&ptr_info);
-    GetSender(std::move(request));
-    std::move(callback).Run(std::move(ptr_info));
+    PendingAssociatedRemote<IntegerSender> remote;
+    GetSender(remote.InitWithNewEndpointAndPassReceiver());
+    std::move(callback).Run(std::move(remote));
   }
 
-  Binding<IntegerSenderConnection>* binding() { return &binding_; }
+  Receiver<IntegerSenderConnection>* receiver() { return &receiver_; }
 
  private:
   static void DeleteSender(IntegerSenderImpl* sender) { delete sender; }
 
-  Binding<IntegerSenderConnection> binding_;
+  Receiver<IntegerSenderConnection> receiver_;
 };
 
 class AssociatedInterfaceTest : public testing::Test {
  public:
   AssociatedInterfaceTest()
       : main_runner_(base::ThreadTaskRunnerHandle::Get()) {}
-  ~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); }
-
-  void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+  ~AssociatedInterfaceTest() override = default;
 
   template <typename T>
-  AssociatedInterfacePtrInfo<T> EmulatePassingAssociatedPtrInfo(
-      AssociatedInterfacePtrInfo<T> ptr_info,
+  PendingAssociatedRemote<T> EmulatePassingAssociatedRemote(
+      PendingAssociatedRemote<T> remote,
       scoped_refptr<MultiplexRouter> source,
       scoped_refptr<MultiplexRouter> target) {
-    ScopedInterfaceEndpointHandle handle = ptr_info.PassHandle();
+    ScopedInterfaceEndpointHandle handle = remote.PassHandle();
     CHECK(handle.pending_association());
     auto id = source->AssociateInterface(std::move(handle));
-    return AssociatedInterfacePtrInfo<T>(target->CreateLocalEndpointHandle(id),
-                                         ptr_info.version());
+    return PendingAssociatedRemote<T>(target->CreateLocalEndpointHandle(id),
+                                      remote.version());
   }
 
   void CreateRouterPair(scoped_refptr<MultiplexRouter>* router0,
@@ -127,32 +121,20 @@
 
   void CreateIntegerSenderWithExistingRouters(
       scoped_refptr<MultiplexRouter> router0,
-      IntegerSenderAssociatedPtrInfo* ptr_info0,
+      PendingAssociatedRemote<IntegerSender>* remote0,
       scoped_refptr<MultiplexRouter> router1,
-      IntegerSenderAssociatedRequest* request1) {
-    *request1 = MakeRequest(ptr_info0);
-    *ptr_info0 = EmulatePassingAssociatedPtrInfo(std::move(*ptr_info0), router1,
-                                                 router0);
+      PendingAssociatedReceiver<IntegerSender>* receiver1) {
+    *receiver1 = remote0->InitWithNewEndpointAndPassReceiver();
+    *remote0 =
+        EmulatePassingAssociatedRemote(std::move(*remote0), router1, router0);
   }
 
-  void CreateIntegerSender(IntegerSenderAssociatedPtrInfo* ptr_info,
-                           IntegerSenderAssociatedRequest* request) {
+  void CreateIntegerSender(PendingAssociatedRemote<IntegerSender>* remote,
+                           PendingAssociatedReceiver<IntegerSender>* receiver) {
     scoped_refptr<MultiplexRouter> router0;
     scoped_refptr<MultiplexRouter> router1;
     CreateRouterPair(&router0, &router1);
-    CreateIntegerSenderWithExistingRouters(router1, ptr_info, router0, request);
-  }
-
-  // Okay to call from any thread.
-  void QuitRunLoop(base::RunLoop* run_loop) {
-    if (main_runner_->RunsTasksInCurrentSequence()) {
-      run_loop->Quit();
-    } else {
-      main_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&AssociatedInterfaceTest::QuitRunLoop,
-                         base::Unretained(this), base::Unretained(run_loop)));
-    }
+    CreateIntegerSenderWithExistingRouters(router1, remote, router0, receiver);
   }
 
  private:
@@ -160,31 +142,6 @@
   scoped_refptr<base::SequencedTaskRunner> main_runner_;
 };
 
-void DoSetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
-  *flag = true;
-  closure.Run();
-}
-
-void DoExpectValueSetFlagAndRunClosure(int32_t expected_value,
-                                       bool* flag,
-                                       const base::Closure& closure,
-                                       int32_t value) {
-  EXPECT_EQ(expected_value, value);
-  DoSetFlagAndRunClosure(flag, closure);
-}
-
-base::Closure SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
-  return base::Bind(&DoSetFlagAndRunClosure, flag, closure);
-}
-
-base::Callback<void(int32_t)> ExpectValueSetFlagAndRunClosure(
-    int32_t expected_value,
-    bool* flag,
-    const base::Closure& closure) {
-  return base::Bind(
-      &DoExpectValueSetFlagAndRunClosure, expected_value, flag, closure);
-}
-
 void Fail() {
   FAIL() << "Unexpected connection error";
 }
@@ -197,51 +154,58 @@
   scoped_refptr<MultiplexRouter> router1;
   CreateRouterPair(&router0, &router1);
 
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, &request);
+  PendingAssociatedReceiver<IntegerSender> receiver;
+  PendingAssociatedRemote<IntegerSender> remote;
 
-  IntegerSenderImpl impl0(std::move(request));
-  AssociatedInterfacePtr<IntegerSender> ptr0;
-  ptr0.Bind(std::move(ptr_info));
+  CreateIntegerSenderWithExistingRouters(router1, &remote, router0, &receiver);
+  IntegerSenderImpl impl0(std::move(receiver));
+  AssociatedRemote<IntegerSender> remote0(std::move(remote));
 
-  CreateIntegerSenderWithExistingRouters(router0, &ptr_info, router1, &request);
-
-  IntegerSenderImpl impl1(std::move(request));
-  AssociatedInterfacePtr<IntegerSender> ptr1;
-  ptr1.Bind(std::move(ptr_info));
+  CreateIntegerSenderWithExistingRouters(router0, &remote, router1, &receiver);
+  IntegerSenderImpl impl1(std::move(receiver));
+  AssociatedRemote<IntegerSender> remote1(std::move(remote));
 
   base::RunLoop run_loop, run_loop2;
-  bool ptr0_callback_run = false;
-  ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
-                                                  run_loop.QuitClosure()));
+  bool remote0_callback_run = false;
+  remote0->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(123, value);
+                  remote0_callback_run = true;
+                  run_loop.Quit();
+                }));
 
-  bool ptr1_callback_run = false;
-  ptr1->Echo(456, ExpectValueSetFlagAndRunClosure(456, &ptr1_callback_run,
-                                                  run_loop2.QuitClosure()));
+  bool remote1_callback_run = false;
+  remote1->Echo(456, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(456, value);
+                  remote1_callback_run = true;
+                  run_loop2.Quit();
+                }));
 
   run_loop.Run();
   run_loop2.Run();
-  EXPECT_TRUE(ptr0_callback_run);
-  EXPECT_TRUE(ptr1_callback_run);
+  EXPECT_TRUE(remote0_callback_run);
+  EXPECT_TRUE(remote1_callback_run);
 
-  bool ptr0_error_callback_run = false;
+  bool remote0_disconnect_handler_run = false;
   base::RunLoop run_loop3;
-  ptr0.set_connection_error_handler(
-      SetFlagAndRunClosure(&ptr0_error_callback_run, run_loop3.QuitClosure()));
+  remote0.set_disconnect_handler(base::BindLambdaForTesting([&] {
+    remote0_disconnect_handler_run = true;
+    run_loop3.Quit();
+  }));
 
-  impl0.binding()->Close();
+  impl0.receiver()->reset();
   run_loop3.Run();
-  EXPECT_TRUE(ptr0_error_callback_run);
+  EXPECT_TRUE(remote0_disconnect_handler_run);
 
-  bool impl1_error_callback_run = false;
+  bool remote1_disconnect_handler_run = false;
   base::RunLoop run_loop4;
-  impl1.binding()->set_connection_error_handler(
-      SetFlagAndRunClosure(&impl1_error_callback_run, run_loop4.QuitClosure()));
+  impl1.receiver()->set_disconnect_handler(base::BindLambdaForTesting([&] {
+    remote1_disconnect_handler_run = true;
+    run_loop4.Quit();
+  }));
 
-  ptr1.reset();
+  remote1.reset();
   run_loop4.Run();
-  EXPECT_TRUE(impl1_error_callback_run);
+  EXPECT_TRUE(remote1_disconnect_handler_run);
 }
 
 class TestSender {
@@ -252,12 +216,12 @@
         max_value_to_send_(-1) {}
 
   // The following three methods are called on the corresponding sender thread.
-  void SetUp(IntegerSenderAssociatedPtrInfo ptr_info,
+  void SetUp(PendingAssociatedRemote<IntegerSender> remote,
              TestSender* next_sender,
              int32_t max_value_to_send) {
     CHECK(task_runner()->RunsTasksInCurrentSequence());
 
-    ptr_.Bind(std::move(ptr_info));
+    remote_.Bind(std::move(remote));
     next_sender_ = next_sender ? next_sender : this;
     max_value_to_send_ = max_value_to_send;
   }
@@ -268,7 +232,7 @@
     if (value > max_value_to_send_)
       return;
 
-    ptr_->Send(value);
+    remote_->Send(value);
 
     next_sender_->task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&TestSender::Send,
@@ -278,7 +242,7 @@
   void TearDown() {
     CHECK(task_runner()->RunsTasksInCurrentSequence());
 
-    ptr_.reset();
+    remote_.reset();
   }
 
   base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
@@ -288,7 +252,7 @@
   TestSender* next_sender_;
   int32_t max_value_to_send_;
 
-  AssociatedInterfacePtr<IntegerSender> ptr_;
+  AssociatedRemote<IntegerSender> remote_;
 };
 
 class TestReceiver {
@@ -297,21 +261,21 @@
       : task_runner_(base::CreateSequencedTaskRunnerWithTraits({})),
         expected_calls_(0) {}
 
-  void SetUp(AssociatedInterfaceRequest<IntegerSender> request0,
-             AssociatedInterfaceRequest<IntegerSender> request1,
+  void SetUp(PendingAssociatedReceiver<IntegerSender> receiver0,
+             PendingAssociatedReceiver<IntegerSender> receiver1,
              size_t expected_calls,
-             const base::Closure& notify_finish) {
+             base::OnceClosure notify_finish) {
     CHECK(task_runner()->RunsTasksInCurrentSequence());
 
-    impl0_.reset(new IntegerSenderImpl(std::move(request0)));
+    impl0_.reset(new IntegerSenderImpl(std::move(receiver0)));
     impl0_->set_notify_send_method_called(
         base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this)));
-    impl1_.reset(new IntegerSenderImpl(std::move(request1)));
+    impl1_.reset(new IntegerSenderImpl(std::move(receiver1)));
     impl1_->set_notify_send_method_called(
         base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this)));
 
     expected_calls_ = expected_calls;
-    notify_finish_ = notify_finish;
+    notify_finish_ = std::move(notify_finish);
   }
 
   void TearDown() {
@@ -329,7 +293,7 @@
     values_.push_back(value);
 
     if (values_.size() >= expected_calls_)
-      notify_finish_.Run();
+      std::move(notify_finish_).Run();
   }
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
@@ -340,17 +304,17 @@
 
   std::vector<int32_t> values_;
 
-  base::Closure notify_finish_;
+  base::OnceClosure notify_finish_;
 };
 
 class NotificationCounter {
  public:
-  NotificationCounter(size_t total_count, const base::Closure& notify_finish)
+  NotificationCounter(size_t total_count, base::OnceClosure notify_finish)
       : total_count_(total_count),
         current_count_(0),
-        notify_finish_(notify_finish) {}
+        notify_finish_(std::move(notify_finish)) {}
 
-  ~NotificationCounter() {}
+  ~NotificationCounter() = default;
 
   // Okay to call from any thread.
   void OnGotNotification() {
@@ -363,14 +327,14 @@
     }
 
     if (finshed)
-      notify_finish_.Run();
+      std::move(notify_finish_).Run();
   }
 
  private:
   base::Lock lock_;
   const size_t total_count_;
   size_t current_count_;
-  base::Closure notify_finish_;
+  base::OnceClosure notify_finish_;
 };
 
 TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
@@ -384,11 +348,11 @@
   scoped_refptr<MultiplexRouter> router1;
   CreateRouterPair(&router0, &router1);
 
-  AssociatedInterfaceRequest<IntegerSender> requests[4];
-  IntegerSenderAssociatedPtrInfo ptr_infos[4];
+  PendingAssociatedReceiver<IntegerSender> pending_receivers[4];
+  PendingAssociatedRemote<IntegerSender> pending_remotes[4];
   for (size_t i = 0; i < 4; ++i) {
-    CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0,
-                                           &requests[i]);
+    CreateIntegerSenderWithExistingRouters(router1, &pending_remotes[i],
+                                           router0, &pending_receivers[i]);
   }
 
   TestSender senders[4];
@@ -396,24 +360,22 @@
     senders[i].task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&TestSender::SetUp, base::Unretained(&senders[i]),
-                       std::move(ptr_infos[i]), nullptr,
+                       std::move(pending_remotes[i]), nullptr,
                        kMaxValue * (i + 1) / 4));
   }
 
   base::RunLoop run_loop;
   TestReceiver receivers[2];
-  NotificationCounter counter(
-      2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                    base::Unretained(this), base::Unretained(&run_loop)));
+  NotificationCounter counter(2, run_loop.QuitClosure());
   for (size_t i = 0; i < 2; ++i) {
     receivers[i].task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
-                       std::move(requests[2 * i]),
-                       std::move(requests[2 * i + 1]),
+                       std::move(pending_receivers[2 * i]),
+                       std::move(pending_receivers[2 * i + 1]),
                        static_cast<size_t>(kMaxValue / 2),
-                       base::Bind(&NotificationCounter::OnGotNotification,
-                                  base::Unretained(&counter))));
+                       base::BindOnce(&NotificationCounter::OnGotNotification,
+                                      base::Unretained(&counter))));
   }
 
   for (size_t i = 0; i < 4; ++i) {
@@ -429,9 +391,8 @@
     base::RunLoop run_loop;
     senders[i].task_runner()->PostTaskAndReply(
         FROM_HERE,
-        base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
-        base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                   base::Unretained(this), base::Unretained(&run_loop)));
+        base::BindOnce(&TestSender::TearDown, base::Unretained(&senders[i])),
+        run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -439,9 +400,9 @@
     base::RunLoop run_loop;
     receivers[i].task_runner()->PostTaskAndReply(
         FROM_HERE,
-        base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
-        base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                   base::Unretained(this), base::Unretained(&run_loop)));
+        base::BindOnce(&TestReceiver::TearDown,
+                       base::Unretained(&receivers[i])),
+        run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -471,11 +432,11 @@
   scoped_refptr<MultiplexRouter> router1;
   CreateRouterPair(&router0, &router1);
 
-  AssociatedInterfaceRequest<IntegerSender> requests[4];
-  IntegerSenderAssociatedPtrInfo ptr_infos[4];
+  PendingAssociatedReceiver<IntegerSender> pending_receivers[4];
+  PendingAssociatedRemote<IntegerSender> pending_remotes[4];
   for (size_t i = 0; i < 4; ++i) {
-    CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0,
-                                           &requests[i]);
+    CreateIntegerSenderWithExistingRouters(router1, &pending_remotes[i],
+                                           router0, &pending_receivers[i]);
   }
 
   TestSender senders[4];
@@ -483,24 +444,22 @@
     senders[i].task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&TestSender::SetUp, base::Unretained(&senders[i]),
-                       std::move(ptr_infos[i]),
+                       std::move(pending_remotes[i]),
                        base::Unretained(&senders[(i + 1) % 4]), kMaxValue));
   }
 
   base::RunLoop run_loop;
   TestReceiver receivers[2];
-  NotificationCounter counter(
-      2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                    base::Unretained(this), base::Unretained(&run_loop)));
+  NotificationCounter counter(2, run_loop.QuitClosure());
   for (size_t i = 0; i < 2; ++i) {
     receivers[i].task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
-                       std::move(requests[2 * i]),
-                       std::move(requests[2 * i + 1]),
+                       std::move(pending_receivers[2 * i]),
+                       std::move(pending_receivers[2 * i + 1]),
                        static_cast<size_t>(kMaxValue / 2),
-                       base::Bind(&NotificationCounter::OnGotNotification,
-                                  base::Unretained(&counter))));
+                       base::BindOnce(&NotificationCounter::OnGotNotification,
+                                      base::Unretained(&counter))));
   }
 
   senders[0].task_runner()->PostTask(
@@ -513,9 +472,8 @@
     base::RunLoop run_loop;
     senders[i].task_runner()->PostTaskAndReply(
         FROM_HERE,
-        base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
-        base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                   base::Unretained(this), base::Unretained(&run_loop)));
+        base::BindOnce(&TestSender::TearDown, base::Unretained(&senders[i])),
+        run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -523,9 +481,9 @@
     base::RunLoop run_loop;
     receivers[i].task_runner()->PostTaskAndReply(
         FROM_HERE,
-        base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
-        base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
-                   base::Unretained(this), base::Unretained(&run_loop)));
+        base::BindOnce(&TestReceiver::TearDown,
+                       base::Unretained(&receivers[i])),
+        run_loop.QuitClosure());
     run_loop.Run();
   }
 
@@ -538,109 +496,104 @@
   }
 }
 
-void CaptureInt32(int32_t* storage,
-                  const base::Closure& closure,
-                  int32_t value) {
-  *storage = value;
-  closure.Run();
-}
-
-void CaptureSenderPtrInfo(IntegerSenderAssociatedPtr* storage,
-                          const base::Closure& closure,
-                          IntegerSenderAssociatedPtrInfo info) {
-  storage->Bind(std::move(info));
-  closure.Run();
-}
-
 TEST_F(AssociatedInterfaceTest, PassAssociatedInterfaces) {
-  IntegerSenderConnectionPtr connection_ptr;
-  IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+  Remote<IntegerSenderConnection> connection_remote;
+  IntegerSenderConnectionImpl connection(
+      connection_remote.BindNewPipeAndPassReceiver());
 
-  IntegerSenderAssociatedPtr sender0;
-  connection_ptr->GetSender(MakeRequest(&sender0));
+  AssociatedRemote<IntegerSender> sender0;
+  connection_remote->GetSender(sender0.BindNewEndpointAndPassReceiver());
 
-  int32_t echoed_value = 0;
   base::RunLoop run_loop;
-  sender0->Echo(123, base::Bind(&CaptureInt32, &echoed_value,
-                                run_loop.QuitClosure()));
+  sender0->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(123, value);
+                  run_loop.Quit();
+                }));
   run_loop.Run();
-  EXPECT_EQ(123, echoed_value);
 
-  IntegerSenderAssociatedPtr sender1;
+  AssociatedRemote<IntegerSender> sender1;
   base::RunLoop run_loop2;
-  connection_ptr->AsyncGetSender(
-      base::Bind(&CaptureSenderPtrInfo, &sender1, run_loop2.QuitClosure()));
+  connection_remote->AsyncGetSender(base::BindLambdaForTesting(
+      [&](PendingAssociatedRemote<IntegerSender> sender) {
+        sender1.Bind(std::move(sender));
+        run_loop2.Quit();
+      }));
   run_loop2.Run();
   EXPECT_TRUE(sender1);
 
   base::RunLoop run_loop3;
-  sender1->Echo(456, base::Bind(&CaptureInt32, &echoed_value,
-                                run_loop3.QuitClosure()));
+  sender1->Echo(456, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(456, value);
+                  run_loop3.Quit();
+                }));
   run_loop3.Run();
-  EXPECT_EQ(456, echoed_value);
 }
 
-TEST_F(AssociatedInterfaceTest, BindingWaitAndPauseWhenNoAssociatedInterfaces) {
-  IntegerSenderConnectionPtr connection_ptr;
-  IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+TEST_F(AssociatedInterfaceTest,
+       ReceiverWaitAndPauseWhenNoAssociatedInterfaces) {
+  Remote<IntegerSenderConnection> connection_remote;
+  IntegerSenderConnectionImpl connection(
+      connection_remote.BindNewPipeAndPassReceiver());
 
-  IntegerSenderAssociatedPtr sender0;
-  connection_ptr->GetSender(MakeRequest(&sender0));
+  AssociatedRemote<IntegerSender> sender0;
+  connection_remote->GetSender(sender0.BindNewEndpointAndPassReceiver());
 
-  EXPECT_FALSE(connection.binding()->HasAssociatedInterfaces());
+  EXPECT_FALSE(
+      connection.receiver()->internal_state()->HasAssociatedInterfaces());
+
   // There are no associated interfaces running on the pipe yet. It is okay to
   // pause.
-  connection.binding()->PauseIncomingMethodCallProcessing();
-  connection.binding()->ResumeIncomingMethodCallProcessing();
+  connection.receiver()->Pause();
+  connection.receiver()->Resume();
 
   // There are no associated interfaces running on the pipe yet. It is okay to
   // wait.
-  EXPECT_TRUE(connection.binding()->WaitForIncomingMethodCall());
+  EXPECT_TRUE(connection.receiver()->WaitForIncomingCall());
 
   // The previous wait has dispatched the GetSender request message, therefore
   // an associated interface has been set up on the pipe. It is not allowed to
   // wait or pause.
-  EXPECT_TRUE(connection.binding()->HasAssociatedInterfaces());
+  EXPECT_TRUE(
+      connection.receiver()->internal_state()->HasAssociatedInterfaces());
 }
 
 class PingServiceImpl : public PingService {
  public:
-  explicit PingServiceImpl(PingServiceAssociatedRequest request)
-      : binding_(this, std::move(request)) {}
-  ~PingServiceImpl() override {}
+  explicit PingServiceImpl(PendingAssociatedReceiver<PingService> receiver)
+      : receiver_(this, std::move(receiver)) {}
+  ~PingServiceImpl() override = default;
 
-  AssociatedBinding<PingService>& binding() { return binding_; }
+  AssociatedReceiver<PingService>& receiver() { return receiver_; }
 
-  void set_ping_handler(const base::Closure& handler) {
-    ping_handler_ = handler;
+  void set_ping_handler(base::RepeatingClosure handler) {
+    ping_handler_ = std::move(handler);
   }
 
   // PingService:
   void Ping(PingCallback callback) override {
-    if (!ping_handler_.is_null())
+    if (ping_handler_)
       ping_handler_.Run();
     std::move(callback).Run();
   }
 
  private:
-  AssociatedBinding<PingService> binding_;
-  base::Closure ping_handler_;
+  AssociatedReceiver<PingService> receiver_;
+  base::RepeatingClosure ping_handler_;
 };
 
 class PingProviderImpl : public AssociatedPingProvider {
  public:
-  explicit PingProviderImpl(AssociatedPingProviderRequest request)
-      : binding_(this, std::move(request)) {}
-  ~PingProviderImpl() override {}
+  explicit PingProviderImpl(PendingReceiver<AssociatedPingProvider> receiver)
+      : receiver_(this, std::move(receiver)) {}
+  ~PingProviderImpl() override = default;
 
   // AssociatedPingProvider:
-  void GetPing(PingServiceAssociatedRequest request) override {
-    ping_services_.emplace_back(new PingServiceImpl(std::move(request)));
+  void GetPing(PendingAssociatedReceiver<PingService> receiver) override {
+    ping_services_.emplace_back(new PingServiceImpl(std::move(receiver)));
 
-    if (expected_bindings_count_ > 0 &&
-        ping_services_.size() == expected_bindings_count_ &&
-        !quit_waiting_.is_null()) {
-      expected_bindings_count_ = 0;
+    if (expected_receivers_count_ > 0 &&
+        ping_services_.size() == expected_receivers_count_ && quit_waiting_) {
+      expected_receivers_count_ = 0;
       std::move(quit_waiting_).Run();
     }
   }
@@ -649,30 +602,29 @@
     return ping_services_;
   }
 
-  void WaitForBindings(size_t count) {
-    DCHECK(quit_waiting_.is_null());
-
-    expected_bindings_count_ = count;
+  void WaitForReceivers(size_t count) {
+    DCHECK(!quit_waiting_);
+    expected_receivers_count_ = count;
     base::RunLoop loop;
     quit_waiting_ = loop.QuitClosure();
     loop.Run();
   }
 
  private:
-  Binding<AssociatedPingProvider> binding_;
+  Receiver<AssociatedPingProvider> receiver_;
   std::vector<std::unique_ptr<PingServiceImpl>> ping_services_;
-  size_t expected_bindings_count_ = 0;
-  base::Closure quit_waiting_;
+  size_t expected_receivers_count_ = 0;
+  base::OnceClosure quit_waiting_;
 };
 
 class CallbackFilter : public MessageReceiver {
  public:
-  explicit CallbackFilter(const base::Closure& callback)
-      : callback_(callback) {}
-  ~CallbackFilter() override {}
+  explicit CallbackFilter(base::RepeatingClosure callback)
+      : callback_(std::move(callback)) {}
+  ~CallbackFilter() override = default;
 
-  static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) {
-    return std::make_unique<CallbackFilter>(callback);
+  static std::unique_ptr<CallbackFilter> Wrap(base::RepeatingClosure callback) {
+    return std::make_unique<CallbackFilter>(std::move(callback));
   }
 
   // MessageReceiver:
@@ -682,51 +634,67 @@
   }
 
  private:
-  const base::Closure callback_;
+  base::RepeatingClosure callback_;
 };
 
-// Verifies that filters work as expected on associated bindings, i.e. that
+// Verifies that filters work as expected on associated receivers, i.e. that
 // they're notified in order, before dispatch; and that each associated
-// binding in a group operates with its own set of filters.
-TEST_F(AssociatedInterfaceTest, BindingWithFilters) {
-  AssociatedPingProviderPtr provider;
-  PingProviderImpl provider_impl(MakeRequest(&provider));
+// receiver in a group operates with its own set of filters.
+TEST_F(AssociatedInterfaceTest, ReceiverWithFilters) {
+  Remote<AssociatedPingProvider> provider;
+  PingProviderImpl provider_impl(provider.BindNewPipeAndPassReceiver());
 
-  PingServiceAssociatedPtr ping_a, ping_b;
-  provider->GetPing(MakeRequest(&ping_a));
-  provider->GetPing(MakeRequest(&ping_b));
-  provider_impl.WaitForBindings(2);
+  AssociatedRemote<PingService> ping_a, ping_b;
+  provider->GetPing(ping_a.BindNewEndpointAndPassReceiver());
+  provider->GetPing(ping_b.BindNewEndpointAndPassReceiver());
+  provider_impl.WaitForReceivers(2);
 
   ASSERT_EQ(2u, provider_impl.ping_services().size());
   PingServiceImpl& ping_a_impl = *provider_impl.ping_services()[0];
   PingServiceImpl& ping_b_impl = *provider_impl.ping_services()[1];
 
-  int a_status, b_status;
-  auto handler_helper = [] (int* a_status, int* b_status, int expected_a_status,
-                            int new_a_status, int expected_b_status,
-                            int new_b_status) {
-    EXPECT_EQ(expected_a_status, *a_status);
-    EXPECT_EQ(expected_b_status, *b_status);
-    *a_status = new_a_status;
-    *b_status = new_b_status;
-  };
-  auto create_handler = [&] (int expected_a_status, int new_a_status,
-                             int expected_b_status, int new_b_status) {
-    return base::Bind(handler_helper, &a_status, &b_status, expected_a_status,
-                      new_a_status, expected_b_status, new_b_status);
-  };
+  int a_status = 0;
+  int b_status = 0;
 
-  ping_a_impl.binding().AddFilter(
-      CallbackFilter::Wrap(create_handler(0, 1, 0, 0)));
-  ping_a_impl.binding().AddFilter(
-      CallbackFilter::Wrap(create_handler(1, 2, 0, 0)));
-  ping_a_impl.set_ping_handler(create_handler(2, 3, 0, 0));
+  ping_a_impl.receiver().AddFilter(
+      CallbackFilter::Wrap(base::BindLambdaForTesting([&] {
+        EXPECT_EQ(0, a_status);
+        EXPECT_EQ(0, b_status);
+        a_status = 1;
+      })));
 
-  ping_b_impl.binding().AddFilter(
-      CallbackFilter::Wrap(create_handler(3, 3, 0, 1)));
-  ping_b_impl.binding().AddFilter(
-      CallbackFilter::Wrap(create_handler(3, 3, 1, 2)));
-  ping_b_impl.set_ping_handler(create_handler(3, 3, 2, 3));
+  ping_a_impl.receiver().AddFilter(
+      CallbackFilter::Wrap(base::BindLambdaForTesting([&] {
+        EXPECT_EQ(1, a_status);
+        EXPECT_EQ(0, b_status);
+        a_status = 2;
+      })));
+
+  ping_a_impl.set_ping_handler(base::BindLambdaForTesting([&] {
+    EXPECT_EQ(2, a_status);
+    EXPECT_EQ(0, b_status);
+    a_status = 3;
+  }));
+
+  ping_b_impl.receiver().AddFilter(
+      CallbackFilter::Wrap(base::BindLambdaForTesting([&] {
+        EXPECT_EQ(3, a_status);
+        EXPECT_EQ(0, b_status);
+        b_status = 1;
+      })));
+
+  ping_b_impl.receiver().AddFilter(
+      CallbackFilter::Wrap(base::BindLambdaForTesting([&] {
+        EXPECT_EQ(3, a_status);
+        EXPECT_EQ(1, b_status);
+        b_status = 2;
+      })));
+
+  ping_b_impl.set_ping_handler(base::BindLambdaForTesting([&] {
+    EXPECT_EQ(3, a_status);
+    EXPECT_EQ(2, b_status);
+    b_status = 3;
+  }));
 
   for (int i = 0; i < 10; ++i) {
     a_status = 0;
@@ -752,437 +720,356 @@
   }
 }
 
-TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTesting) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
+TEST_F(AssociatedInterfaceTest, AssociatedRemoteFlushForTesting) {
+  PendingAssociatedReceiver<IntegerSender> receiver;
+  PendingAssociatedRemote<IntegerSender> remote;
+  CreateIntegerSender(&remote, &receiver);
 
-  IntegerSenderImpl impl0(std::move(request));
-  AssociatedInterfacePtr<IntegerSender> ptr0;
-  ptr0.Bind(std::move(ptr_info));
-  ptr0.set_connection_error_handler(base::Bind(&Fail));
+  IntegerSenderImpl impl0(std::move(receiver));
+  AssociatedRemote<IntegerSender> remote0(std::move(remote));
+  remote0.set_disconnect_handler(base::BindOnce(&Fail));
 
-  bool ptr0_callback_run = false;
-  ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
-                                                  base::DoNothing()));
-  ptr0.FlushForTesting();
-  EXPECT_TRUE(ptr0_callback_run);
+  bool remote0_callback_run = false;
+  remote0->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(123, value);
+                  remote0_callback_run = true;
+                }));
+  remote0.FlushForTesting();
+  EXPECT_TRUE(remote0_callback_run);
 }
 
-void SetBool(bool* value) {
-  *value = true;
-}
+TEST_F(AssociatedInterfaceTest, AssociatedRemoteFlushForTestingWithClosedPeer) {
+  PendingAssociatedReceiver<IntegerSender> receiver;
+  PendingAssociatedRemote<IntegerSender> remote;
+  CreateIntegerSender(&remote, &receiver);
 
-template <typename T>
-void SetBoolWithUnusedParameter(bool* value, T unused) {
-  *value = true;
-}
-
-TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTestingWithClosedPeer) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
-
-  AssociatedInterfacePtr<IntegerSender> ptr0;
-  ptr0.Bind(std::move(ptr_info));
+  AssociatedRemote<IntegerSender> remote0(std::move(remote));
   bool called = false;
-  ptr0.set_connection_error_handler(base::Bind(&SetBool, &called));
-  request = nullptr;
+  remote0.set_disconnect_handler(
+      base::BindLambdaForTesting([&] { called = true; }));
+  receiver.reset();
 
-  ptr0.FlushForTesting();
+  remote0.FlushForTesting();
   EXPECT_TRUE(called);
-  ptr0.FlushForTesting();
+  remote0.FlushForTesting();
 }
 
 TEST_F(AssociatedInterfaceTest, AssociatedBindingFlushForTesting) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
+  PendingAssociatedReceiver<IntegerSender> receiver;
+  PendingAssociatedRemote<IntegerSender> remote;
+  CreateIntegerSender(&remote, &receiver);
 
-  IntegerSenderImpl impl0(std::move(request));
-  impl0.set_connection_error_handler(base::Bind(&Fail));
-  AssociatedInterfacePtr<IntegerSender> ptr0;
-  ptr0.Bind(std::move(ptr_info));
+  IntegerSenderImpl impl0(std::move(receiver));
+  impl0.receiver()->set_disconnect_handler(base::BindOnce(&Fail));
+  AssociatedRemote<IntegerSender> remote0(std::move(remote));
 
-  bool ptr0_callback_run = false;
-  ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
-                                                  base::DoNothing()));
-  // Because the flush is sent from the binding, it only guarantees that the
+  bool remote0_callback_run = false;
+  remote0->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                  EXPECT_EQ(123, value);
+                  remote0_callback_run = true;
+                }));
+
+  // Because the flush is sent from the receiver, it only guarantees that the
   // request has been received, not the response. The second flush waits for the
   // response to be received.
-  impl0.binding()->FlushForTesting();
-  impl0.binding()->FlushForTesting();
-  EXPECT_TRUE(ptr0_callback_run);
+  impl0.receiver()->FlushForTesting();
+  impl0.receiver()->FlushForTesting();
+  EXPECT_TRUE(remote0_callback_run);
 }
 
 TEST_F(AssociatedInterfaceTest,
-       AssociatedBindingFlushForTestingWithClosedPeer) {
+       AssociatedReceiverFlushForTestingWithClosedPeer) {
   scoped_refptr<MultiplexRouter> router0;
   scoped_refptr<MultiplexRouter> router1;
   CreateRouterPair(&router0, &router1);
 
-  AssociatedInterfaceRequest<IntegerSender> request;
+  PendingAssociatedReceiver<IntegerSender> receiver;
   {
-    IntegerSenderAssociatedPtrInfo ptr_info;
-    CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0,
-                                           &request);
+    PendingAssociatedRemote<IntegerSender> remote;
+    CreateIntegerSenderWithExistingRouters(router1, &remote, router0,
+                                           &receiver);
   }
 
-  IntegerSenderImpl impl(std::move(request));
+  IntegerSenderImpl impl(std::move(receiver));
   bool called = false;
-  impl.set_connection_error_handler(base::Bind(&SetBool, &called));
-  impl.binding()->FlushForTesting();
+  impl.receiver()->set_disconnect_handler(
+      base::BindLambdaForTesting([&] { called = true; }));
+  impl.receiver()->FlushForTesting();
   EXPECT_TRUE(called);
-  impl.binding()->FlushForTesting();
+  impl.receiver()->FlushForTesting();
 }
 
-TEST_F(AssociatedInterfaceTest, BindingFlushForTesting) {
-  IntegerSenderConnectionPtr ptr;
-  IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
+TEST_F(AssociatedInterfaceTest, ReceiverFlushForTesting) {
+  Remote<IntegerSenderConnection> remote;
+  IntegerSenderConnectionImpl impl(remote.BindNewPipeAndPassReceiver());
   bool called = false;
-  ptr->AsyncGetSender(base::Bind(
-      &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called));
+  remote->AsyncGetSender(base::BindLambdaForTesting(
+      [&](PendingAssociatedRemote<IntegerSender> remote) { called = true; }));
   EXPECT_FALSE(called);
-  impl.binding()->set_connection_error_handler(base::Bind(&Fail));
-  // Because the flush is sent from the binding, it only guarantees that the
+  impl.receiver()->set_disconnect_handler(base::BindOnce(&Fail));
+
+  // Because the flush is sent from the receiver, it only guarantees that the
   // request has been received, not the response. The second flush waits for the
   // response to be received.
-  impl.binding()->FlushForTesting();
-  impl.binding()->FlushForTesting();
+  impl.receiver()->FlushForTesting();
+  impl.receiver()->FlushForTesting();
+
   EXPECT_TRUE(called);
 }
 
-TEST_F(AssociatedInterfaceTest, BindingFlushForTestingWithClosedPeer) {
-  IntegerSenderConnectionPtr ptr;
-  IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
+TEST_F(AssociatedInterfaceTest, ReceiverFlushForTestingWithClosedPeer) {
+  Remote<IntegerSenderConnection> remote;
+  IntegerSenderConnectionImpl impl(remote.BindNewPipeAndPassReceiver());
   bool called = false;
-  impl.binding()->set_connection_error_handler(base::Bind(&SetBool, &called));
-  ptr.reset();
+  impl.receiver()->set_disconnect_handler(
+      base::BindLambdaForTesting([&] { called = true; }));
+  remote.reset();
   EXPECT_FALSE(called);
-  impl.binding()->FlushForTesting();
+  impl.receiver()->FlushForTesting();
   EXPECT_TRUE(called);
-  impl.binding()->FlushForTesting();
+  impl.receiver()->FlushForTesting();
 }
 
-TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTesting) {
-  IntegerSenderConnectionPtr ptr;
-  auto binding =
-      MakeStrongBinding(std::make_unique<IntegerSenderConnectionImpl>(
-                            IntegerSenderConnectionRequest{}),
-                        MakeRequest(&ptr));
+TEST_F(AssociatedInterfaceTest, RemoteFlushForTesting) {
+  Remote<IntegerSenderConnection> remote;
+  IntegerSenderConnectionImpl impl(remote.BindNewPipeAndPassReceiver());
   bool called = false;
-  IntegerSenderAssociatedPtr sender_ptr;
-  ptr->GetSender(MakeRequest(&sender_ptr));
-  sender_ptr->Echo(1, base::Bind(&SetBoolWithUnusedParameter<int>, &called));
+  remote.set_disconnect_handler(base::BindOnce(&Fail));
+  remote->AsyncGetSender(base::BindLambdaForTesting(
+      [&](PendingAssociatedRemote<IntegerSender> remote) { called = true; }));
   EXPECT_FALSE(called);
-  // Because the flush is sent from the binding, it only guarantees that the
-  // request has been received, not the response. The second flush waits for the
-  // response to be received.
-  ASSERT_TRUE(binding);
-  binding->FlushForTesting();
-  ASSERT_TRUE(binding);
-  binding->FlushForTesting();
+  remote.FlushForTesting();
   EXPECT_TRUE(called);
 }
 
-TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTestingWithClosedPeer) {
-  IntegerSenderConnectionPtr ptr;
+TEST_F(AssociatedInterfaceTest, RemoteFlushForTestingWithClosedPeer) {
+  Remote<IntegerSenderConnection> remote;
+  ignore_result(remote.BindNewPipeAndPassReceiver());
   bool called = false;
-  auto binding =
-      MakeStrongBinding(std::make_unique<IntegerSenderConnectionImpl>(
-                            IntegerSenderConnectionRequest{}),
-                        MakeRequest(&ptr));
-  binding->set_connection_error_handler(base::Bind(&SetBool, &called));
-  ptr.reset();
+  remote.set_disconnect_handler(
+      base::BindLambdaForTesting([&] { called = true; }));
   EXPECT_FALSE(called);
-  ASSERT_TRUE(binding);
-  binding->FlushForTesting();
+  remote.FlushForTesting();
   EXPECT_TRUE(called);
-  ASSERT_FALSE(binding);
+  remote.FlushForTesting();
 }
 
-TEST_F(AssociatedInterfaceTest, PtrFlushForTesting) {
-  IntegerSenderConnectionPtr ptr;
-  IntegerSenderConnectionImpl impl(MakeRequest(&ptr));
-  bool called = false;
-  ptr.set_connection_error_handler(base::Bind(&Fail));
-  ptr->AsyncGetSender(base::Bind(
-      &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called));
-  EXPECT_FALSE(called);
-  ptr.FlushForTesting();
-  EXPECT_TRUE(called);
-}
+TEST_F(AssociatedInterfaceTest, AssociatedReceiverConnectionErrorWithReason) {
+  PendingAssociatedReceiver<IntegerSender> pending_receiver;
+  PendingAssociatedRemote<IntegerSender> pending_remote;
+  CreateIntegerSender(&pending_remote, &pending_receiver);
 
-TEST_F(AssociatedInterfaceTest, PtrFlushForTestingWithClosedPeer) {
-  IntegerSenderConnectionPtr ptr;
-  MakeRequest(&ptr);
-  bool called = false;
-  ptr.set_connection_error_handler(base::Bind(&SetBool, &called));
-  EXPECT_FALSE(called);
-  ptr.FlushForTesting();
-  EXPECT_TRUE(called);
-  ptr.FlushForTesting();
-}
-
-TEST_F(AssociatedInterfaceTest, AssociatedBindingConnectionErrorWithReason) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
-
-  IntegerSenderImpl impl(std::move(request));
-  AssociatedInterfacePtr<IntegerSender> ptr;
-  ptr.Bind(std::move(ptr_info));
+  IntegerSenderImpl impl(std::move(pending_receiver));
+  AssociatedRemote<IntegerSender> remote(std::move(pending_remote));
 
   base::RunLoop run_loop;
-  impl.binding()->set_connection_error_with_reason_handler(base::Bind(
-      [](const base::Closure& quit_closure, uint32_t custom_reason,
-         const std::string& description) {
-        EXPECT_EQ(123u, custom_reason);
-        EXPECT_EQ("farewell", description);
-        quit_closure.Run();
-      },
-      run_loop.QuitClosure()));
+  impl.receiver()->set_disconnect_with_reason_handler(
+      base::BindLambdaForTesting(
+          [&](uint32_t custom_reason, const std::string& description) {
+            EXPECT_EQ(123u, custom_reason);
+            EXPECT_EQ("farewell", description);
+            run_loop.Quit();
+          }));
 
-  ptr.ResetWithReason(123u, "farewell");
+  remote.ResetWithReason(123u, "farewell");
 
   run_loop.Run();
 }
 
 TEST_F(AssociatedInterfaceTest,
-       PendingAssociatedBindingConnectionErrorWithReason) {
-  // Test that AssociatedBinding is notified with connection error when the
+       PendingAssociatedReceiverConnectionErrorWithReason) {
+  // Test that AssociatedReceiver is notified with connection error when the
   // interface hasn't associated with a message pipe and the peer is closed.
 
-  IntegerSenderAssociatedPtr ptr;
-  IntegerSenderImpl impl(MakeRequest(&ptr));
+  AssociatedRemote<IntegerSender> remote;
+  IntegerSenderImpl impl(remote.BindNewEndpointAndPassReceiver());
 
   base::RunLoop run_loop;
-  impl.binding()->set_connection_error_with_reason_handler(base::Bind(
-      [](const base::Closure& quit_closure, uint32_t custom_reason,
-         const std::string& description) {
-        EXPECT_EQ(123u, custom_reason);
-        EXPECT_EQ("farewell", description);
-        quit_closure.Run();
-      },
-      run_loop.QuitClosure()));
+  impl.receiver()->set_disconnect_with_reason_handler(
+      base::BindLambdaForTesting(
+          [&](uint32_t custom_reason, const std::string& description) {
+            EXPECT_EQ(123u, custom_reason);
+            EXPECT_EQ("farewell", description);
+            run_loop.Quit();
+          }));
 
-  ptr.ResetWithReason(123u, "farewell");
-
+  remote.ResetWithReason(123u, "farewell");
   run_loop.Run();
 }
 
-TEST_F(AssociatedInterfaceTest, AssociatedPtrConnectionErrorWithReason) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
+TEST_F(AssociatedInterfaceTest, AssociatedRemoteConnectionErrorWithReason) {
+  PendingAssociatedReceiver<IntegerSender> pending_receiver;
+  PendingAssociatedRemote<IntegerSender> pending_remote;
+  CreateIntegerSender(&pending_remote, &pending_receiver);
 
-  IntegerSenderImpl impl(std::move(request));
-  AssociatedInterfacePtr<IntegerSender> ptr;
-  ptr.Bind(std::move(ptr_info));
+  IntegerSenderImpl impl(std::move(pending_receiver));
+  AssociatedRemote<IntegerSender> remote(std::move(pending_remote));
 
   base::RunLoop run_loop;
-  ptr.set_connection_error_with_reason_handler(base::Bind(
-      [](const base::Closure& quit_closure, uint32_t custom_reason,
-         const std::string& description) {
+  remote.set_disconnect_with_reason_handler(base::BindLambdaForTesting(
+      [&](uint32_t custom_reason, const std::string& description) {
         EXPECT_EQ(456u, custom_reason);
         EXPECT_EQ("farewell", description);
-        quit_closure.Run();
-      },
-      run_loop.QuitClosure()));
+        run_loop.Quit();
+      }));
 
-  impl.binding()->CloseWithReason(456u, "farewell");
-
+  impl.receiver()->ResetWithReason(456u, "farewell");
   run_loop.Run();
 }
 
-TEST_F(AssociatedInterfaceTest, PendingAssociatedPtrConnectionErrorWithReason) {
+TEST_F(AssociatedInterfaceTest,
+       PendingAssociatedRemoteConnectionErrorWithReason) {
   // Test that AssociatedInterfacePtr is notified with connection error when the
   // interface hasn't associated with a message pipe and the peer is closed.
 
-  IntegerSenderAssociatedPtr ptr;
-  auto request = MakeRequest(&ptr);
+  AssociatedRemote<IntegerSender> remote;
+  auto pending_receiver = remote.BindNewEndpointAndPassReceiver();
 
   base::RunLoop run_loop;
-  ptr.set_connection_error_with_reason_handler(base::Bind(
-      [](const base::Closure& quit_closure, uint32_t custom_reason,
-         const std::string& description) {
+  remote.set_disconnect_with_reason_handler(base::BindLambdaForTesting(
+      [&](uint32_t custom_reason, const std::string& description) {
         EXPECT_EQ(456u, custom_reason);
         EXPECT_EQ("farewell", description);
-        quit_closure.Run();
-      },
-      run_loop.QuitClosure()));
+        run_loop.Quit();
+      }));
 
-  request.ResetWithReason(456u, "farewell");
-
+  pending_receiver.ResetWithReason(456u, "farewell");
   run_loop.Run();
 }
 
 TEST_F(AssociatedInterfaceTest, AssociatedRequestResetWithReason) {
-  AssociatedInterfaceRequest<IntegerSender> request;
-  IntegerSenderAssociatedPtrInfo ptr_info;
-  CreateIntegerSender(&ptr_info, &request);
+  PendingAssociatedReceiver<IntegerSender> pending_receiver;
+  PendingAssociatedRemote<IntegerSender> pending_remote;
+  CreateIntegerSender(&pending_remote, &pending_receiver);
 
-  AssociatedInterfacePtr<IntegerSender> ptr;
-  ptr.Bind(std::move(ptr_info));
+  AssociatedRemote<IntegerSender> remote(std::move(pending_remote));
 
   base::RunLoop run_loop;
-  ptr.set_connection_error_with_reason_handler(base::Bind(
-      [](const base::Closure& quit_closure, uint32_t custom_reason,
-         const std::string& description) {
+  remote.set_disconnect_with_reason_handler(base::BindLambdaForTesting(
+      [&](uint32_t custom_reason, const std::string& description) {
         EXPECT_EQ(789u, custom_reason);
         EXPECT_EQ("long time no see", description);
-        quit_closure.Run();
-      },
-      run_loop.QuitClosure()));
+        run_loop.Quit();
+      }));
 
-  request.ResetWithReason(789u, "long time no see");
+  pending_receiver.ResetWithReason(789u, "long time no see");
 
   run_loop.Run();
 }
 
-TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) {
-  IntegerSenderConnectionPtr connection_ptr;
-  IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+TEST_F(AssociatedInterfaceTest, SharedAssociatedRemote) {
+  Remote<IntegerSenderConnection> connection_remote;
+  IntegerSenderConnectionImpl connection(
+      connection_remote.BindNewPipeAndPassReceiver());
 
-  IntegerSenderAssociatedPtr sender;
-  connection_ptr->GetSender(MakeRequest(&sender));
+  PendingAssociatedRemote<IntegerSender> pending_remote;
+  connection_remote->GetSender(
+      pending_remote.InitWithNewEndpointAndPassReceiver());
 
-  scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_sender =
-      ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender));
+  SharedAssociatedRemote<IntegerSender> shared_sender(
+      std::move(pending_remote));
 
   {
     // Test the thread safe pointer can be used from the interface ptr thread.
-    int32_t echoed_value = 0;
     base::RunLoop run_loop;
-    (*thread_safe_sender)
-        ->Echo(123, base::Bind(&CaptureInt32, &echoed_value,
-                               run_loop.QuitClosure()));
+    shared_sender->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                          EXPECT_EQ(123, value);
+                          run_loop.Quit();
+                        }));
     run_loop.Run();
-    EXPECT_EQ(123, echoed_value);
   }
 
   // Test the thread safe pointer can be used from another thread.
   base::RunLoop run_loop;
 
-  auto run_method = base::Bind(
-      [](const scoped_refptr<base::TaskRunner>& main_task_runner,
-         const base::Closure& quit_closure,
-         const scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr>&
-             thread_safe_sender) {
-        auto done_callback = base::Bind(
-            [](const scoped_refptr<base::TaskRunner>& main_task_runner,
-               const base::Closure& quit_closure,
-               scoped_refptr<base::SequencedTaskRunner> sender_sequence_runner,
-               int32_t result) {
-              EXPECT_EQ(123, result);
-              // Validate the callback is invoked on the calling sequence.
-              EXPECT_TRUE(sender_sequence_runner->RunsTasksInCurrentSequence());
-              // Notify the run_loop to quit.
-              main_task_runner->PostTask(FROM_HERE, quit_closure);
-            });
-        scoped_refptr<base::SequencedTaskRunner> current_sequence_runner =
-            base::SequencedTaskRunnerHandle::Get();
-        (*thread_safe_sender)
-            ->Echo(123, base::Bind(done_callback, main_task_runner,
-                                   quit_closure, current_sequence_runner));
-      },
-      base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
-      thread_safe_sender);
-  base::CreateSequencedTaskRunnerWithTraits({})->PostTask(FROM_HERE,
-                                                          run_method);
+  auto sender_task_runner = base::CreateSequencedTaskRunnerWithTraits({});
+  auto quit_closure = run_loop.QuitClosure();
+  sender_task_runner->PostTask(
+      FROM_HERE, base::BindLambdaForTesting([&] {
+        shared_sender->Echo(
+            123, base::BindLambdaForTesting([&](int32_t value) {
+              EXPECT_EQ(123, value);
+              EXPECT_TRUE(sender_task_runner->RunsTasksInCurrentSequence());
+              std::move(quit_closure).Run();
+            }));
+      }));
 
   // Block until the method callback is called on the background thread.
   run_loop.Run();
 }
 
 struct ForwarderTestContext {
-  IntegerSenderConnectionPtr connection_ptr;
+  Remote<IntegerSenderConnection> connection_remote;
   std::unique_ptr<IntegerSenderConnectionImpl> interface_impl;
-  IntegerSenderAssociatedRequest sender_request;
+  PendingAssociatedReceiver<IntegerSender> sender_receiver;
 };
 
-TEST_F(AssociatedInterfaceTest,
-       ThreadSafeAssociatedInterfacePtrWithTaskRunner) {
+TEST_F(AssociatedInterfaceTest, SharedAssociatedRemoteWithTaskRunner) {
   const scoped_refptr<base::SequencedTaskRunner> other_thread_task_runner =
       base::CreateSequencedTaskRunnerWithTraits({});
 
   ForwarderTestContext* context = new ForwarderTestContext();
-  IntegerSenderAssociatedPtrInfo sender_info;
-  base::WaitableEvent sender_info_bound_event(
+  PendingAssociatedRemote<IntegerSender> pending_remote;
+  base::WaitableEvent sender_bound_event(
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED);
-  auto setup = [](base::WaitableEvent* sender_info_bound_event,
-                  IntegerSenderAssociatedPtrInfo* sender_info,
-                  ForwarderTestContext* context) {
-    context->interface_impl = std::make_unique<IntegerSenderConnectionImpl>(
-        MakeRequest(&context->connection_ptr));
-
-    auto sender_request = MakeRequest(sender_info);
-    context->connection_ptr->GetSender(std::move(sender_request));
-
-    // Unblock the main thread as soon as |sender_info| is set.
-    sender_info_bound_event->Signal();
-  };
   other_thread_task_runner->PostTask(
-      FROM_HERE,
-      base::BindOnce(setup, &sender_info_bound_event, &sender_info, context));
-  sender_info_bound_event.Wait();
+      FROM_HERE, base::BindLambdaForTesting([&] {
+        context->interface_impl = std::make_unique<IntegerSenderConnectionImpl>(
+            context->connection_remote.BindNewPipeAndPassReceiver());
+        context->connection_remote->GetSender(
+            pending_remote.InitWithNewEndpointAndPassReceiver());
+        sender_bound_event.Signal();
+      }));
 
-  // Create a ThreadSafeAssociatedPtr that binds on the background thread and is
-  // associated with |connection_ptr| there.
-  scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_ptr =
-      ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender_info),
-                                                   other_thread_task_runner);
+  sender_bound_event.Wait();
 
-  // Issue a call on the thread-safe ptr immediately. Note that this may happen
+  // Create a SharedAssociatedRemote that binds on the background thread and is
+  // associated with |connection_remote| there.
+  SharedAssociatedRemote<IntegerSender> shared_sender(std::move(pending_remote),
+                                                      other_thread_task_runner);
+
+  // Issue a call on the shared remote immediately. Note that this may happen
   // before the interface is bound on the background thread, and that must be
   // OK.
-  {
-    auto echo_callback =
-        base::Bind([](const base::Closure& quit_closure, int32_t result) {
-          EXPECT_EQ(123, result);
-          quit_closure.Run();
-        });
-    base::RunLoop run_loop;
-    (*thread_safe_ptr)
-        ->Echo(123, base::Bind(echo_callback, run_loop.QuitClosure()));
-
-    // Block until the method callback is called.
-    run_loop.Run();
-  }
+  base::RunLoop run_loop;
+  shared_sender->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                        EXPECT_EQ(123, value);
+                        run_loop.Quit();
+                      }));
+  run_loop.Run();
 
   other_thread_task_runner->DeleteSoon(FROM_HERE, context);
 
-  // Reset the pointer now so the InterfacePtr associated resources can be
-  // deleted before the background thread's message loop is invalidated.
-  thread_safe_ptr = nullptr;
+  shared_sender.reset();
 }
 
 class DiscardingAssociatedPingProviderProvider
     : public AssociatedPingProviderProvider {
  public:
   void GetPingProvider(
-      AssociatedPingProviderAssociatedRequest request) override {}
+      PendingAssociatedReceiver<AssociatedPingProvider> receiver) override {}
 };
 
-TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) {
+TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedReceiver) {
   DiscardingAssociatedPingProviderProvider ping_provider_provider;
-  mojo::Binding<AssociatedPingProviderProvider> binding(
+  mojo::Receiver<AssociatedPingProviderProvider> receiver(
       &ping_provider_provider);
-  AssociatedPingProviderProviderPtr provider_provider;
-  binding.Bind(mojo::MakeRequest(&provider_provider));
-  AssociatedPingProviderAssociatedPtr provider;
-  provider_provider->GetPingProvider(mojo::MakeRequest(&provider));
-  PingServiceAssociatedPtr ping;
-  provider->GetPing(mojo::MakeRequest(&ping));
+  Remote<AssociatedPingProviderProvider> provider_provider;
+  receiver.Bind(provider_provider.BindNewPipeAndPassReceiver());
+  AssociatedRemote<AssociatedPingProvider> provider;
+  provider_provider->GetPingProvider(provider.BindNewEndpointAndPassReceiver());
+  AssociatedRemote<PingService> ping;
+  provider->GetPing(ping.BindNewEndpointAndPassReceiver());
   base::RunLoop run_loop;
-  ping.set_connection_error_handler(run_loop.QuitClosure());
+  ping.set_disconnect_handler(run_loop.QuitClosure());
   run_loop.Run();
 }
 
 TEST_F(AssociatedInterfaceTest, AssociateWithDisconnectedPipe) {
-  IntegerSenderAssociatedPtr sender;
-  AssociateWithDisconnectedPipe(MakeRequest(&sender).PassHandle());
+  AssociatedRemote<IntegerSender> sender;
+  AssociateWithDisconnectedPipe(
+      sender.BindNewEndpointAndPassReceiver().PassHandle());
   sender->Send(42);
 }
 
@@ -1191,20 +1078,21 @@
   // synchronously when the master interface pipe is closed. Regression test for
   // https://crbug.com/864731.
 
-  IntegerSenderConnectionPtr connection_ptr;
-  IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr));
+  Remote<IntegerSenderConnection> connection_remote;
+  IntegerSenderConnectionImpl connection(
+      connection_remote.BindNewPipeAndPassReceiver());
 
   base::RunLoop loop;
   bool error_handler_invoked = false;
-  IntegerSenderAssociatedPtr sender0;
-  connection_ptr->GetSender(MakeRequest(&sender0));
-  sender0.set_connection_error_handler(base::BindLambdaForTesting([&] {
+  AssociatedRemote<IntegerSender> sender0;
+  connection_remote->GetSender(sender0.BindNewEndpointAndPassReceiver());
+  sender0.set_disconnect_handler(base::BindLambdaForTesting([&] {
     error_handler_invoked = true;
     loop.Quit();
   }));
 
   // This should not trigger the error handler synchronously...
-  connection_ptr.reset();
+  connection_remote.reset();
   EXPECT_FALSE(error_handler_invoked);
 
   // ...but it should be triggered once we spin the scheduler.
diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
index 1f401c0..1705ea4 100644
--- a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
@@ -166,9 +166,9 @@
   void set_get_sender_notification(base::OnceClosure notification) {
     get_sender_notification_ = std::move(notification);
   }
-  void GetSender(IntegerSenderAssociatedRequest request) override {
+  void GetSender(PendingAssociatedReceiver<IntegerSender> receiver) override {
     sender_impl_ =
-        std::make_unique<SenderType>(std::move(request), sender_runner_);
+        std::make_unique<SenderType>(std::move(receiver), sender_runner_);
     std::move(get_sender_notification_).Run();
   }
 
diff --git a/mojo/public/cpp/bindings/tests/receiver_set_unittest.cc b/mojo/public/cpp/bindings/tests/receiver_set_unittest.cc
index c8829d4..b88d676 100644
--- a/mojo/public/cpp/bindings/tests/receiver_set_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/receiver_set_unittest.cc
@@ -343,7 +343,7 @@
 
  private:
   // AssociatedPingProvider:
-  void GetPing(AssociatedInterfaceRequest<PingService> receiver) override {
+  void GetPing(PendingAssociatedReceiver<PingService> receiver) override {
     ping_receivers_.Add(this, std::move(receiver), new_ping_context_);
     if (!new_ping_handler_.is_null())
       new_ping_handler_.Run();
diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
index 7e7a47d..9f52fb5 100644
--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
@@ -13,8 +13,11 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/shared_remote.h"
 #include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
 #include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -25,7 +28,7 @@
 
 class TestSyncCommonImpl {
  public:
-  TestSyncCommonImpl() {}
+  TestSyncCommonImpl() = default;
 
   using PingHandler = base::RepeatingCallback<void(base::OnceClosure)>;
   template <typename Func>
@@ -47,18 +50,18 @@
     async_echo_handler_ = base::BindLambdaForTesting(handler);
   }
 
-  using SendInterfaceHandler =
-      base::RepeatingCallback<void(TestSyncAssociatedPtrInfo)>;
+  using SendRemoteHandler =
+      base::RepeatingCallback<void(PendingAssociatedRemote<TestSync>)>;
   template <typename Func>
-  void set_send_interface_handler(Func handler) {
-    send_interface_handler_ = base::BindLambdaForTesting(handler);
+  void set_send_remote_handler(Func handler) {
+    send_remote_handler_ = base::BindLambdaForTesting(handler);
   }
 
-  using SendRequestHandler =
-      base::RepeatingCallback<void(TestSyncAssociatedRequest)>;
+  using SendReceiverHandler =
+      base::RepeatingCallback<void(PendingAssociatedReceiver<TestSync>)>;
   template <typename Func>
-  void set_send_request_handler(Func handler) {
-    send_request_handler_ = base::BindLambdaForTesting(handler);
+  void set_send_receiver_handler(Func handler) {
+    send_receiver_handler_ = base::BindLambdaForTesting(handler);
   }
 
   void PingImpl(base::OnceCallback<void()> callback) {
@@ -83,27 +86,27 @@
     }
     async_echo_handler_.Run(value, std::move(callback));
   }
-  void SendInterfaceImpl(TestSyncAssociatedPtrInfo ptr) {
-    send_interface_handler_.Run(std::move(ptr));
+  void SendRemoteImpl(PendingAssociatedRemote<TestSync> remote) {
+    send_remote_handler_.Run(std::move(remote));
   }
-  void SendRequestImpl(TestSyncAssociatedRequest request) {
-    send_request_handler_.Run(std::move(request));
+  void SendReceiverImpl(PendingAssociatedReceiver<TestSync> receiver) {
+    send_receiver_handler_.Run(std::move(receiver));
   }
 
  private:
   PingHandler ping_handler_;
   EchoHandler echo_handler_;
   AsyncEchoHandler async_echo_handler_;
-  SendInterfaceHandler send_interface_handler_;
-  SendRequestHandler send_request_handler_;
+  SendRemoteHandler send_remote_handler_;
+  SendReceiverHandler send_receiver_handler_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSyncCommonImpl);
 };
 
 class TestSyncImpl : public TestSync, public TestSyncCommonImpl {
  public:
-  explicit TestSyncImpl(TestSyncRequest request)
-      : binding_(this, std::move(request)) {}
+  explicit TestSyncImpl(PendingReceiver<TestSync> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
   // TestSync implementation:
   void Ping(PingCallback callback) override { PingImpl(std::move(callback)); }
@@ -114,18 +117,18 @@
     AsyncEchoImpl(value, std::move(callback));
   }
 
-  Binding<TestSync>* binding() { return &binding_; }
+  Receiver<TestSync>* receiver() { return &receiver_; }
 
  private:
-  Binding<TestSync> binding_;
+  Receiver<TestSync> receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSyncImpl);
 };
 
 class TestSyncMasterImpl : public TestSyncMaster, public TestSyncCommonImpl {
  public:
-  explicit TestSyncMasterImpl(TestSyncMasterRequest request)
-      : binding_(this, std::move(request)) {}
+  explicit TestSyncMasterImpl(PendingReceiver<TestSyncMaster> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
   // TestSyncMaster implementation:
   void Ping(PingCallback callback) override { PingImpl(std::move(callback)); }
@@ -136,24 +139,24 @@
     AsyncEchoImpl(value, std::move(callback));
   }
   void SendRemote(PendingAssociatedRemote<TestSync> remote) override {
-    SendInterfaceImpl(std::move(remote));
+    SendRemoteImpl(std::move(remote));
   }
   void SendReceiver(PendingAssociatedReceiver<TestSync> receiver) override {
-    SendRequestImpl(std::move(receiver));
+    SendReceiverImpl(std::move(receiver));
   }
 
-  Binding<TestSyncMaster>* binding() { return &binding_; }
+  Receiver<TestSyncMaster>* receiver() { return &receiver_; }
 
  private:
-  Binding<TestSyncMaster> binding_;
+  Receiver<TestSyncMaster> receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSyncMasterImpl);
 };
 
 class TestSyncAssociatedImpl : public TestSync, public TestSyncCommonImpl {
  public:
-  explicit TestSyncAssociatedImpl(TestSyncAssociatedRequest request)
-      : binding_(this, std::move(request)) {}
+  explicit TestSyncAssociatedImpl(PendingAssociatedReceiver<TestSync> receiver)
+      : receiver_(this, std::move(receiver)) {}
 
   // TestSync implementation:
   void Ping(PingCallback callback) override { PingImpl(std::move(callback)); }
@@ -164,10 +167,10 @@
     AsyncEchoImpl(value, std::move(callback));
   }
 
-  AssociatedBinding<TestSync>* binding() { return &binding_; }
+  AssociatedReceiver<TestSync>* receiver() { return &receiver_; }
 
  private:
-  AssociatedBinding<TestSync> binding_;
+  AssociatedReceiver<TestSync> receiver_;
 
   DISALLOW_COPY_AND_ASSIGN(TestSyncAssociatedImpl);
 };
@@ -188,56 +191,56 @@
 template <typename Interface>
 using ImplTypeFor = typename ImplTraits<Interface>::Type;
 
-// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr>
-// that exposes the InterfacePtr interface.
+// A wrapper for either a Remote or SharedRemote that exposes the Remote
+// interface.
 template <typename Interface>
-class PtrWrapper {
+class RemoteWrapper {
  public:
-  explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {}
+  explicit RemoteWrapper(Remote<Interface> remote)
+      : remote_(std::move(remote)) {}
 
-  explicit PtrWrapper(
-      scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr)
-      : thread_safe_ptr_(thread_safe_ptr) {}
+  explicit RemoteWrapper(SharedRemote<Interface> shared_remote)
+      : shared_remote_(std::move(shared_remote)) {}
 
-  PtrWrapper(PtrWrapper&& other) = default;
+  RemoteWrapper(RemoteWrapper&& other) = default;
 
   Interface* operator->() {
-    return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get();
+    return shared_remote_ ? shared_remote_.get() : remote_.get();
   }
 
-  void set_connection_error_handler(const base::Closure& error_handler) {
-    DCHECK(!thread_safe_ptr_);
-    ptr_.set_connection_error_handler(error_handler);
+  void set_disconnect_handler(base::OnceClosure handler) {
+    DCHECK(!shared_remote_);
+    remote_.set_disconnect_handler(std::move(handler));
   }
 
   void reset() {
-    ptr_ = nullptr;
-    thread_safe_ptr_ = nullptr;
+    remote_.reset();
+    shared_remote_.reset();
   }
 
  private:
-  InterfacePtr<Interface> ptr_;
-  scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr_;
+  Remote<Interface> remote_;
+  SharedRemote<Interface> shared_remote_;
 
-  DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
+  DISALLOW_COPY_AND_ASSIGN(RemoteWrapper);
 };
 
 // The type parameter for SyncMethodCommonTests and
 // SyncMethodOnSequenceCommonTests for varying the Interface and whether to use
-// InterfacePtr or ThreadSafeInterfacePtr.
+// Remote or SharedRemote.
 template <typename InterfaceT,
-          bool use_thread_safe_ptr,
+          bool use_shared_remote,
           BindingsTestSerializationMode serialization_mode>
 struct TestParams {
   using Interface = InterfaceT;
-  static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr;
+  static const bool kIsSharedRemoteTest = use_shared_remote;
 
-  static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) {
-    if (kIsThreadSafeInterfacePtrTest) {
-      return PtrWrapper<Interface>(
-          ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr)));
+  static RemoteWrapper<InterfaceT> Wrap(PendingRemote<Interface> remote) {
+    if (kIsSharedRemoteTest) {
+      return RemoteWrapper<Interface>(
+          SharedRemote<Interface>(std::move(remote)));
     } else {
-      return PtrWrapper<Interface>(std::move(ptr));
+      return RemoteWrapper<Interface>(Remote<Interface>(std::move(remote)));
     }
   }
 
@@ -254,7 +257,7 @@
 
   void SetUp(InterfaceRequest<Interface> request) {
     CHECK(task_runner()->RunsTasksInCurrentSequence());
-    impl_.reset(new ImplTypeFor<Interface>(std::move(request)));
+    impl_ = std::make_unique<ImplTypeFor<Interface>>(std::move(request));
     impl_->set_ping_handler([this](typename Interface::PingCallback callback) {
       {
         base::AutoLock locker(lock_);
@@ -288,8 +291,8 @@
 
 class SyncMethodTest : public testing::Test {
  public:
-  SyncMethodTest() {}
-  ~SyncMethodTest() override { base::RunLoop().RunUntilIdle(); }
+  SyncMethodTest() = default;
+  ~SyncMethodTest() override = default;
 
  protected:
   base::test::ScopedTaskEnvironment task_environment;
@@ -298,8 +301,8 @@
 template <typename TypeParam>
 class SyncMethodCommonTest : public SyncMethodTest {
  public:
-  SyncMethodCommonTest() {}
-  ~SyncMethodCommonTest() override {}
+  SyncMethodCommonTest() = default;
+  ~SyncMethodCommonTest() override = default;
 
   void SetUp() override {
     BindingsTestBase::SetupSerializationBehavior(TypeParam::kSerializationMode);
@@ -308,76 +311,56 @@
 
 class SyncMethodAssociatedTest : public SyncMethodTest {
  public:
-  SyncMethodAssociatedTest() {}
-  ~SyncMethodAssociatedTest() override {}
+  SyncMethodAssociatedTest() = default;
+  ~SyncMethodAssociatedTest() override = default;
 
  protected:
   void SetUp() override {
-    master_impl_.reset(new TestSyncMasterImpl(MakeRequest(&master_ptr_)));
+    master_impl_ = std::make_unique<TestSyncMasterImpl>(
+        master_remote_.BindNewPipeAndPassReceiver());
 
-    asso_request_ = MakeRequest(&asso_ptr_info_);
-    opposite_asso_request_ = MakeRequest(&opposite_asso_ptr_info_);
+    impl_pending_sync_receiver_ =
+        impl_pending_sync_remote_.InitWithNewEndpointAndPassReceiver();
+    client_pending_sync_receiver_ =
+        client_pending_sync_remote_.InitWithNewEndpointAndPassReceiver();
 
-    master_impl_->set_send_interface_handler(
-        [this](TestSyncAssociatedPtrInfo ptr) {
-          opposite_asso_ptr_info_ = std::move(ptr);
+    master_impl_->set_send_remote_handler(
+        [this](PendingAssociatedRemote<TestSync> remote) {
+          client_pending_sync_remote_ = std::move(remote);
         });
     base::RunLoop run_loop;
-    master_impl_->set_send_request_handler(
-        [this, &run_loop](TestSyncAssociatedRequest request) {
-          asso_request_ = std::move(request);
+    master_impl_->set_send_receiver_handler(
+        [this, &run_loop](PendingAssociatedReceiver<TestSync> receiver) {
+          impl_pending_sync_receiver_ = std::move(receiver);
           run_loop.Quit();
         });
 
-    master_ptr_->SendRemote(std::move(opposite_asso_ptr_info_));
-    master_ptr_->SendReceiver(std::move(asso_request_));
+    master_remote_->SendRemote(std::move(client_pending_sync_remote_));
+    master_remote_->SendReceiver(std::move(impl_pending_sync_receiver_));
     run_loop.Run();
   }
 
   void TearDown() override {
-    asso_ptr_info_ = TestSyncAssociatedPtrInfo();
-    asso_request_ = TestSyncAssociatedRequest();
-    opposite_asso_ptr_info_ = TestSyncAssociatedPtrInfo();
-    opposite_asso_request_ = TestSyncAssociatedRequest();
-
-    master_ptr_ = nullptr;
+    impl_pending_sync_remote_.reset();
+    impl_pending_sync_receiver_.reset();
+    client_pending_sync_remote_.reset();
+    client_pending_sync_receiver_.reset();
+    master_remote_.reset();
     master_impl_.reset();
   }
 
-  InterfacePtr<TestSyncMaster> master_ptr_;
+  Remote<TestSyncMaster> master_remote_;
   std::unique_ptr<TestSyncMasterImpl> master_impl_;
 
-  // An associated interface whose binding lives at the |master_impl_| side.
-  TestSyncAssociatedPtrInfo asso_ptr_info_;
-  TestSyncAssociatedRequest asso_request_;
+  // An associated interface whose receiver lives at the |master_impl_| side.
+  PendingAssociatedRemote<TestSync> impl_pending_sync_remote_;
+  PendingAssociatedReceiver<TestSync> impl_pending_sync_receiver_;
 
-  // An associated interface whose binding lives at the |master_ptr_| side.
-  TestSyncAssociatedPtrInfo opposite_asso_ptr_info_;
-  TestSyncAssociatedRequest opposite_asso_request_;
+  // An associated interface whose receiver lives at the |master_remote_| side.
+  PendingAssociatedRemote<TestSync> client_pending_sync_remote_;
+  PendingAssociatedReceiver<TestSync> client_pending_sync_receiver_;
 };
 
-void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
-  *flag = true;
-  closure.Run();
-}
-
-void ExpectValueAndRunClosure(int32_t expected_value,
-                              const base::Closure& closure,
-                              int32_t value) {
-  EXPECT_EQ(expected_value, value);
-  closure.Run();
-}
-
-template <typename Func>
-void CallAsyncEchoCallback(Func func, int32_t value) {
-  func(value);
-}
-
-template <typename Func>
-TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) {
-  return base::Bind(&CallAsyncEchoCallback<Func>, func);
-}
-
 class SequencedTaskRunnerTestBase;
 
 void RunTestOnSequencedTaskRunner(
@@ -400,26 +383,26 @@
  protected:
   void Done() {
     TearDown();
-    task_runner_->PostTask(FROM_HERE, quit_closure_);
+    task_runner_->PostTask(FROM_HERE, std::move(quit_closure_));
     delete this;
   }
 
-  base::Closure DoneClosure() {
-    return base::Bind(&SequencedTaskRunnerTestBase::Done,
-                      base::Unretained(this));
+  base::OnceClosure DoneClosure() {
+    return base::BindOnce(&SequencedTaskRunnerTestBase::Done,
+                          base::Unretained(this));
   }
 
  private:
   friend void RunTestOnSequencedTaskRunner(
       std::unique_ptr<SequencedTaskRunnerTestBase> test);
 
-  void Init(const base::Closure& quit_closure) {
+  void Init(base::OnceClosure quit_closure) {
     task_runner_ = base::SequencedTaskRunnerHandle::Get();
-    quit_closure_ = quit_closure;
+    quit_closure_ = std::move(quit_closure);
   }
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  base::Closure quit_closure_;
+  base::OnceClosure quit_closure_;
 };
 
 // A helper class to launch tests on a SequencedTaskRunner. This is necessary
@@ -437,11 +420,11 @@
   void SetUp() override {
     BindingsTestBase::SetupSerializationBehavior(TypeParam::kSerializationMode);
     impl_ = std::make_unique<ImplTypeFor<typename TypeParam::Interface>>(
-        MakeRequest(&ptr_));
+        remote_.BindNewPipeAndPassReceiver());
   }
 
  protected:
-  InterfacePtr<typename TypeParam::Interface> ptr_;
+  Remote<typename TypeParam::Interface> remote_;
   std::unique_ptr<ImplTypeFor<typename TypeParam::Interface>> impl_;
 };
 
@@ -458,10 +441,9 @@
 
 // TestSync (without associated interfaces) and TestSyncMaster (with associated
 // interfaces) exercise MultiplexRouter with different configurations.
-// Each test is run once with an InterfacePtr and once with a
-// ThreadSafeInterfacePtr to ensure that they behave the same with respect to
-// sync calls. Finally, all such combinations are tested in different message
-// serialization modes.
+// Each test is run once with a Remote and once with a SharedRemote to ensure
+// that they behave the same with respect to sync calls. Finally, all such
+// combinations are tested in different message serialization modes.
 using InterfaceTypes = testing::Types<
     TestParams<TestSync,
                true,
@@ -501,13 +483,15 @@
 
 TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   base::RunLoop run_loop;
-  ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123,
-                            run_loop.QuitClosure()));
+  wrapped_remote->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                         EXPECT_EQ(123, value);
+                         run_loop.Quit();
+                       }));
   run_loop.Run();
 }
 
@@ -543,247 +527,240 @@
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
                                  CallSyncMethodAsynchronously) {
-  this->ptr_->Echo(
-      123, base::Bind(&ExpectValueAndRunClosure, 123, this->DoneClosure()));
+  this->remote_->Echo(123, base::BindLambdaForTesting([&](int32_t value) {
+                        EXPECT_EQ(123, value);
+                        this->DoneClosure().Run();
+                      }));
 }
 
 TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) {
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  InterfaceRequest<Interface> request = MakeRequest(&interface_ptr);
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  auto receiver = remote.InitWithNewPipeAndPassReceiver();
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   TestSyncServiceSequence<Interface> service_sequence;
   service_sequence.task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&TestSyncServiceSequence<Interface>::SetUp,
-                     base::Unretained(&service_sequence), std::move(request)));
-  ASSERT_TRUE(ptr->Ping());
+                     base::Unretained(&service_sequence), std::move(receiver)));
+  ASSERT_TRUE(wrapped_remote->Ping());
   ASSERT_TRUE(service_sequence.ping_called());
 
   int32_t output_value = -1;
-  ASSERT_TRUE(ptr->Echo(42, &output_value));
+  ASSERT_TRUE(wrapped_remote->Echo(42, &output_value));
   ASSERT_EQ(42, output_value);
 
   base::RunLoop run_loop;
   service_sequence.task_runner()->PostTaskAndReply(
       FROM_HERE,
-      base::Bind(&TestSyncServiceSequence<Interface>::TearDown,
-                 base::Unretained(&service_sequence)),
+      base::BindOnce(&TestSyncServiceSequence<Interface>::TearDown,
+                     base::Unretained(&service_sequence)),
       run_loop.QuitClosure());
   run_loop.Run();
 }
 
-TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) {
-  // Test that an interface pointer waiting for a sync call response can be
-  // reentered by a binding serving sync methods on the same thread.
+TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodReceiver) {
+  // Test that a remote waiting for a sync call response can be reentered by a
+  // receiver serving sync methods on the same thread.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
+  PendingRemote<Interface> remote;
   // The binding lives on the same thread as the interface pointer.
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
   int32_t output_value = -1;
-  ASSERT_TRUE(ptr->Echo(42, &output_value));
+  ASSERT_TRUE(wrapped_remote->Echo(42, &output_value));
   EXPECT_EQ(42, output_value);
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
-                                 ReenteredBySyncMethodBinding) {
+                                 ReenteredBySyncMethodReceiver) {
   // Test that an interface pointer waiting for a sync call response can be
   // reentered by a binding serving sync methods on the same thread.
 
   int32_t output_value = -1;
-  ASSERT_TRUE(this->ptr_->Echo(42, &output_value));
+  ASSERT_TRUE(this->remote_->Echo(42, &output_value));
   EXPECT_EQ(42, output_value);
   this->Done();
 }
 
-TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) {
-  // Test that it won't result in crash or hang if an interface pointer is
-  // destroyed while it is waiting for a sync call response.
+TYPED_TEST(SyncMethodCommonTest, RemoteDestroyedDuringSyncCall) {
+  // Test that it won't result in crash or hang if a remote is destroyed while
+  // waiting for a sync call response.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
-  impl.set_ping_handler([&ptr](TestSync::PingCallback callback) {
-    ptr.reset();
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
+  impl.set_ping_handler([&](TestSync::PingCallback callback) {
+    wrapped_remote.reset();
     std::move(callback).Run();
   });
-  ASSERT_FALSE(ptr->Ping());
+  ASSERT_FALSE(wrapped_remote->Ping());
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
-                                 InterfacePtrDestroyedDuringSyncCall) {
-  // Test that it won't result in crash or hang if an interface pointer is
-  // destroyed while it is waiting for a sync call response.
+                                 RemoteDestroyedDuringSyncCall) {
+  // Test that it won't result in crash or hang if a remote is destroyed while
+  // waiting for a sync call response.
 
-  auto* ptr = &this->ptr_;
-  this->impl_->set_ping_handler([ptr](TestSync::PingCallback callback) {
-    ptr->reset();
+  this->impl_->set_ping_handler([&](TestSync::PingCallback callback) {
+    this->remote_.reset();
     std::move(callback).Run();
   });
-  ASSERT_FALSE(this->ptr_->Ping());
+  ASSERT_FALSE(this->remote_->Ping());
   this->Done();
 }
 
-TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) {
-  // Test that it won't result in crash or hang if a binding is
-  // closed (and therefore the message pipe handle is closed) while the
-  // corresponding interface pointer is waiting for a sync call response.
+TYPED_TEST(SyncMethodCommonTest, ReceiverDestroyedDuringSyncCall) {
+  // Test that it won't result in crash or hang if a receiver is reset
+  // (and therefore the message pipe handle is closed) while its corresponding
+  // remote is waiting for a sync call response.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
-  impl.set_ping_handler([&impl](TestSync::PingCallback callback) {
-    impl.binding()->Close();
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
+  impl.set_ping_handler([&](TestSync::PingCallback callback) {
+    impl.receiver()->reset();
     std::move(callback).Run();
   });
-  ASSERT_FALSE(ptr->Ping());
+  ASSERT_FALSE(wrapped_remote->Ping());
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
-                                 BindingDestroyedDuringSyncCall) {
-  // Test that it won't result in crash or hang if a binding is
-  // closed (and therefore the message pipe handle is closed) while the
-  // corresponding interface pointer is waiting for a sync call response.
+                                 ReceiverDestroyedDuringSyncCall) {
+  // Test that it won't result in crash or hang if a receiver is reset
+  // (and therefore the message pipe handle is closed) while its corresponding
+  // remote is waiting for a sync call response.
 
-  auto& impl = *this->impl_;
-  this->impl_->set_ping_handler([&impl](TestSync::PingCallback callback) {
-    impl.binding()->Close();
+  this->impl_->set_ping_handler([&](TestSync::PingCallback callback) {
+    this->impl_->receiver()->reset();
     std::move(callback).Run();
   });
-  ASSERT_FALSE(this->ptr_->Ping());
+  ASSERT_FALSE(this->remote_->Ping());
   this->Done();
 }
 
 TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) {
-  // Test that we can call a sync method on an interface ptr, while there is
-  // already a sync call ongoing. The responses arrive in order.
+  // Test that we can call a sync method on a remote, while there is already a
+  // sync call ongoing. The responses arrive in order.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
   int32_t result_value = -1;
 
   bool first_call = true;
-  impl.set_echo_handler([&first_call, &ptr, &result_value](
-                            int32_t value, TestSync::EchoCallback callback) {
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
     if (first_call) {
       first_call = false;
-      ASSERT_TRUE(ptr->Echo(456, &result_value));
+      ASSERT_TRUE(wrapped_remote->Echo(456, &result_value));
       EXPECT_EQ(456, result_value);
     }
     std::move(callback).Run(value);
   });
 
-  ASSERT_TRUE(ptr->Echo(123, &result_value));
+  ASSERT_TRUE(wrapped_remote->Echo(123, &result_value));
   EXPECT_EQ(123, result_value);
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
                                  NestedSyncCallsWithInOrderResponses) {
-  // Test that we can call a sync method on an interface ptr, while there is
-  // already a sync call ongoing. The responses arrive in order.
+  // Test that we can call a sync method on a remote, while there is already a
+  // sync call ongoing. The responses arrive in order.
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
   int32_t result_value = -1;
 
   bool first_call = true;
-  auto& ptr = this->ptr_;
-  auto& impl = *this->impl_;
-  impl.set_echo_handler([&first_call, &ptr, &result_value](
-                            int32_t value, TestSync::EchoCallback callback) {
-    if (first_call) {
-      first_call = false;
-      ASSERT_TRUE(ptr->Echo(456, &result_value));
-      EXPECT_EQ(456, result_value);
-    }
-    std::move(callback).Run(value);
-  });
+  this->impl_->set_echo_handler(
+      [&](int32_t value, TestSync::EchoCallback callback) {
+        if (first_call) {
+          first_call = false;
+          ASSERT_TRUE(this->remote_->Echo(456, &result_value));
+          EXPECT_EQ(456, result_value);
+        }
+        std::move(callback).Run(value);
+      });
 
-  ASSERT_TRUE(ptr->Echo(123, &result_value));
+  ASSERT_TRUE(this->remote_->Echo(123, &result_value));
   EXPECT_EQ(123, result_value);
   this->Done();
 }
 
 TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) {
-  // Test that we can call a sync method on an interface ptr, while there is
+  // Test that we can call a sync method on a remote, while there is
   // already a sync call ongoing. The responses arrive out of order.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
   int32_t result_value = -1;
 
   bool first_call = true;
-  impl.set_echo_handler([&first_call, &ptr, &result_value](
-                            int32_t value, TestSync::EchoCallback callback) {
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
     std::move(callback).Run(value);
     if (first_call) {
       first_call = false;
-      ASSERT_TRUE(ptr->Echo(456, &result_value));
+      ASSERT_TRUE(wrapped_remote->Echo(456, &result_value));
       EXPECT_EQ(456, result_value);
     }
   });
 
-  ASSERT_TRUE(ptr->Echo(123, &result_value));
+  ASSERT_TRUE(wrapped_remote->Echo(123, &result_value));
   EXPECT_EQ(123, result_value);
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
                                  NestedSyncCallsWithOutOfOrderResponses) {
-  // Test that we can call a sync method on an interface ptr, while there is
-  // already a sync call ongoing. The responses arrive out of order.
+  // Test that we can call a sync method on a remote while there is already a
+  // sync call ongoing. The responses arrive out of order.
 
   // The same variable is used to store the output of the two sync calls, in
   // order to test that responses are handled in the correct order.
   int32_t result_value = -1;
 
   bool first_call = true;
-  auto& ptr = this->ptr_;
-  auto& impl = *this->impl_;
-  impl.set_echo_handler([&first_call, &ptr, &result_value](
-                            int32_t value, TestSync::EchoCallback callback) {
-    std::move(callback).Run(value);
-    if (first_call) {
-      first_call = false;
-      ASSERT_TRUE(ptr->Echo(456, &result_value));
-      EXPECT_EQ(456, result_value);
-    }
-  });
+  this->impl_->set_echo_handler(
+      [&](int32_t value, TestSync::EchoCallback callback) {
+        std::move(callback).Run(value);
+        if (first_call) {
+          first_call = false;
+          ASSERT_TRUE(this->remote_->Echo(456, &result_value));
+          EXPECT_EQ(456, result_value);
+        }
+      });
 
-  ASSERT_TRUE(ptr->Echo(123, &result_value));
+  ASSERT_TRUE(this->remote_->Echo(123, &result_value));
   EXPECT_EQ(123, result_value);
   this->Done();
 }
 
 TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async responses are queued until the sync call completes.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // responses are queued until the sync call completes.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   int32_t async_echo_request_value = -1;
   TestSync::AsyncEchoCallback async_echo_request_callback;
   base::RunLoop run_loop1;
   impl.set_async_echo_handler(
-      [&async_echo_request_value, &async_echo_request_callback, &run_loop1](
-          int32_t value, TestSync::AsyncEchoCallback callback) {
+      [&](int32_t value, TestSync::AsyncEchoCallback callback) {
         async_echo_request_value = value;
         async_echo_request_callback = std::move(callback);
         run_loop1.Quit();
@@ -791,29 +768,25 @@
 
   bool async_echo_response_dispatched = false;
   base::RunLoop run_loop2;
-  ptr->AsyncEcho(
-      123,
-      BindAsyncEchoCallback(
-         [&async_echo_response_dispatched, &run_loop2](int32_t result) {
-           async_echo_response_dispatched = true;
-           EXPECT_EQ(123, result);
-           run_loop2.Quit();
-         }));
+  wrapped_remote->AsyncEcho(123,
+                            base::BindLambdaForTesting([&](int32_t result) {
+                              async_echo_response_dispatched = true;
+                              EXPECT_EQ(123, result);
+                              run_loop2.Quit();
+                            }));
   // Run until the AsyncEcho request reaches the service side.
   run_loop1.Run();
 
-  impl.set_echo_handler(
-      [&async_echo_request_value, &async_echo_request_callback](
-          int32_t value, TestSync::EchoCallback callback) {
-        // Send back the async response first.
-        EXPECT_FALSE(async_echo_request_callback.is_null());
-        std::move(async_echo_request_callback).Run(async_echo_request_value);
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
+    // Send back the async response first.
+    EXPECT_FALSE(async_echo_request_callback.is_null());
+    std::move(async_echo_request_callback).Run(async_echo_request_value);
 
-        std::move(callback).Run(value);
-      });
+    std::move(callback).Run(value);
+  });
 
   int32_t result_value = -1;
-  ASSERT_TRUE(ptr->Echo(456, &result_value));
+  ASSERT_TRUE(wrapped_remote->Echo(456, &result_value));
   EXPECT_EQ(456, result_value);
 
   // Although the AsyncEcho response arrives before the Echo response, it should
@@ -828,8 +801,8 @@
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
                                    AsyncResponseQueuedDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async responses are queued until the sync call completes.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // responses are queued until the sync call completes.
 
   void Run() override {
     this->impl_->set_async_echo_handler(
@@ -839,12 +812,13 @@
           OnAsyncEchoReceived();
         });
 
-    this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
-                            async_echo_response_dispatched_ = true;
-                            EXPECT_EQ(123, result);
-                            EXPECT_TRUE(async_echo_response_dispatched_);
-                            this->Done();
-                          }));
+    this->remote_->AsyncEcho(123,
+                             base::BindLambdaForTesting([&](int32_t result) {
+                               async_echo_response_dispatched_ = true;
+                               EXPECT_EQ(123, result);
+                               EXPECT_TRUE(async_echo_response_dispatched_);
+                               this->Done();
+                             }));
   }
 
   // Called when the AsyncEcho request reaches the service side.
@@ -859,7 +833,7 @@
     });
 
     int32_t result_value = -1;
-    ASSERT_TRUE(this->ptr_->Echo(456, &result_value));
+    ASSERT_TRUE(this->remote_->Echo(456, &result_value));
     EXPECT_EQ(456, result_value);
 
     // Although the AsyncEcho response arrives before the Echo response, it
@@ -873,36 +847,32 @@
 };
 
 TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async requests for a binding running on the same thread are queued
-  // until the sync call completes.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // requests for a binding running on the same thread are queued until the sync
+  // call completes.
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> interface_ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.InitWithNewPipeAndPassReceiver());
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
   bool async_echo_request_dispatched = false;
   impl.set_async_echo_handler(
-      [&async_echo_request_dispatched](int32_t value,
-                                       TestSync::AsyncEchoCallback callback) {
+      [&](int32_t value, TestSync::AsyncEchoCallback callback) {
         async_echo_request_dispatched = true;
         std::move(callback).Run(value);
       });
 
   bool async_echo_response_dispatched = false;
   base::RunLoop run_loop;
-  ptr->AsyncEcho(
-      123,
-      BindAsyncEchoCallback(
-         [&async_echo_response_dispatched, &run_loop](int32_t result) {
-           async_echo_response_dispatched = true;
-           EXPECT_EQ(123, result);
-           run_loop.Quit();
-         }));
+  wrapped_remote->AsyncEcho(123,
+                            base::BindLambdaForTesting([&](int32_t result) {
+                              async_echo_response_dispatched = true;
+                              EXPECT_EQ(123, result);
+                              run_loop.Quit();
+                            }));
 
-  impl.set_echo_handler([&async_echo_request_dispatched](
-                            int32_t value, TestSync::EchoCallback callback) {
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
     // Although the AsyncEcho request is sent before the Echo request, it
     // shouldn't be dispatched yet at this point, because there is an ongoing
     // sync call on the same thread.
@@ -911,7 +881,7 @@
   });
 
   int32_t result_value = -1;
-  ASSERT_TRUE(ptr->Echo(456, &result_value));
+  ASSERT_TRUE(wrapped_remote->Echo(456, &result_value));
   EXPECT_EQ(456, result_value);
 
   // Although the AsyncEcho request is sent before the Echo request, it
@@ -926,9 +896,9 @@
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
                                    AsyncRequestQueuedDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async requests for a binding running on the same thread are queued
-  // until the sync call completes.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // requests for a binding running on the same thread are queued until the sync
+  // call completes.
   void Run() override {
     this->impl_->set_async_echo_handler(
         [this](int32_t value, TestSync::AsyncEchoCallback callback) {
@@ -936,23 +906,23 @@
           std::move(callback).Run(value);
         });
 
-    this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
-                            EXPECT_EQ(123, result);
-                            this->Done();
-                          }));
+    this->remote_->AsyncEcho(123,
+                             base::BindLambdaForTesting([&](int32_t result) {
+                               EXPECT_EQ(123, result);
+                               this->Done();
+                             }));
 
     this->impl_->set_echo_handler(
         [this](int32_t value, TestSync::EchoCallback callback) {
           // Although the AsyncEcho request is sent before the Echo request, it
           // shouldn't be dispatched yet at this point, because there is an
-          // ongoing
-          // sync call on the same thread.
+          // ongoing sync call on the same thread.
           EXPECT_FALSE(async_echo_request_dispatched_);
           std::move(callback).Run(value);
         });
 
     int32_t result_value = -1;
-    ASSERT_TRUE(this->ptr_->Echo(456, &result_value));
+    ASSERT_TRUE(this->remote_->Echo(456, &result_value));
     EXPECT_EQ(456, result_value);
 
     // Although the AsyncEcho request is sent before the Echo request, it
@@ -964,68 +934,64 @@
 
 TYPED_TEST(SyncMethodCommonTest,
            QueuedMessagesProcessedBeforeErrorNotification) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async responses are queued. If the message pipe is disconnected
-  // before the queued messages are processed, the connection error
-  // notification is delayed until all the queued messages are processed.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // responses are queued. If the message pipe is disconnected before the queued
+  // queued messages are processed, the disconnect handler is delayed until all
+  // the queued messages are processed.
 
-  // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before
-  // error notifications, so skip it for this test.
-  if (TypeParam::kIsThreadSafeInterfacePtrTest)
+  // SharedRemote doesn't guarantee that messages are delivered before the
+  // disconnect handler, so skip it for this test.
+  if (TypeParam::kIsSharedRemoteTest)
     return;
 
   using Interface = typename TypeParam::Interface;
-  InterfacePtr<Interface> ptr;
-  ImplTypeFor<Interface> impl(MakeRequest(&ptr));
+  Remote<Interface> remote;
+  ImplTypeFor<Interface> impl(remote.BindNewPipeAndPassReceiver());
 
   int32_t async_echo_request_value = -1;
   TestSync::AsyncEchoCallback async_echo_request_callback;
   base::RunLoop run_loop1;
   impl.set_async_echo_handler(
-      [&async_echo_request_value, &async_echo_request_callback, &run_loop1](
-          int32_t value, TestSync::AsyncEchoCallback callback) {
+      [&](int32_t value, TestSync::AsyncEchoCallback callback) {
         async_echo_request_value = value;
         async_echo_request_callback = std::move(callback);
         run_loop1.Quit();
       });
 
   bool async_echo_response_dispatched = false;
-  bool connection_error_dispatched = false;
+  bool disconnect_dispatched = false;
   base::RunLoop run_loop2;
-  ptr->AsyncEcho(123, BindAsyncEchoCallback([&async_echo_response_dispatched,
-                                             &connection_error_dispatched, &ptr,
-                                             &run_loop2](int32_t result) {
-                   async_echo_response_dispatched = true;
-                   // At this point, error notification should not be dispatched
-                   // yet.
-                   EXPECT_FALSE(connection_error_dispatched);
-                   EXPECT_FALSE(ptr.encountered_error());
-                   EXPECT_EQ(123, result);
-                   run_loop2.Quit();
-                 }));
+  remote->AsyncEcho(123, base::BindLambdaForTesting([&](int32_t result) {
+                      async_echo_response_dispatched = true;
+                      // At this point, error notification should not be
+                      // dispatched yet.
+                      EXPECT_FALSE(disconnect_dispatched);
+                      EXPECT_TRUE(remote.is_connected());
+                      EXPECT_EQ(123, result);
+                      run_loop2.Quit();
+                    }));
   // Run until the AsyncEcho request reaches the service side.
   run_loop1.Run();
 
-  impl.set_echo_handler(
-      [&impl, &async_echo_request_value, &async_echo_request_callback](
-          int32_t value, TestSync::EchoCallback callback) {
-        // Send back the async response first.
-        EXPECT_FALSE(async_echo_request_callback.is_null());
-        std::move(async_echo_request_callback).Run(async_echo_request_value);
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
+    // Send back the async response first.
+    EXPECT_FALSE(async_echo_request_callback.is_null());
+    std::move(async_echo_request_callback).Run(async_echo_request_value);
 
-        impl.binding()->Close();
-      });
+    impl.receiver()->reset();
+  });
 
   base::RunLoop run_loop3;
-  ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure,
-                                              &connection_error_dispatched,
-                                              run_loop3.QuitClosure()));
+  remote.set_disconnect_handler(base::BindLambdaForTesting([&] {
+    disconnect_dispatched = true;
+    run_loop3.Quit();
+  }));
 
   int32_t result_value = -1;
-  ASSERT_FALSE(ptr->Echo(456, &result_value));
+  ASSERT_FALSE(remote->Echo(456, &result_value));
   EXPECT_EQ(-1, result_value);
-  ASSERT_FALSE(connection_error_dispatched);
-  EXPECT_FALSE(ptr.encountered_error());
+  ASSERT_FALSE(disconnect_dispatched);
+  EXPECT_TRUE(remote.is_connected());
 
   // Although the AsyncEcho response arrives before the Echo response, it should
   // be queued and not yet dispatched.
@@ -1039,17 +1005,17 @@
   // Run until the error notification is dispatched.
   run_loop3.Run();
 
-  ASSERT_TRUE(connection_error_dispatched);
-  EXPECT_TRUE(ptr.encountered_error());
+  ASSERT_TRUE(disconnect_dispatched);
+  EXPECT_FALSE(remote.is_connected());
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST_F(
     SyncMethodOnSequenceCommonTest,
     QueuedMessagesProcessedBeforeErrorNotification) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, async responses are queued. If the message pipe is disconnected
-  // before the queued messages are processed, the connection error
-  // notification is delayed until all the queued messages are processed.
+  // Test that while a remote is waiting for the response to a sync call, async
+  // responses are queued. If the message pipe is disconnected before the queued
+  // messages are processed, the disconnect notification is delayed until all
+  // the queued messages are processed.
 
   void Run() override {
     this->impl_->set_async_echo_handler(
@@ -1057,16 +1023,16 @@
           OnAsyncEchoReachedService(value, std::move(callback));
         });
 
-    this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
-                            async_echo_response_dispatched_ = true;
-                            // At this point, error notification should not be
-                            // dispatched
-                            // yet.
-                            EXPECT_FALSE(connection_error_dispatched_);
-                            EXPECT_FALSE(this->ptr_.encountered_error());
-                            EXPECT_EQ(123, result);
-                            EXPECT_TRUE(async_echo_response_dispatched_);
-                          }));
+    this->remote_->AsyncEcho(123,
+                             base::BindLambdaForTesting([&](int32_t result) {
+                               async_echo_response_dispatched_ = true;
+                               // At this point, error notification should not
+                               // be dispatched yet.
+                               EXPECT_FALSE(disconnect_dispatched_);
+                               EXPECT_TRUE(this->remote_.is_connected());
+                               EXPECT_EQ(123, result);
+                               EXPECT_TRUE(async_echo_response_dispatched_);
+                             }));
   }
 
   void OnAsyncEchoReachedService(int32_t value,
@@ -1079,19 +1045,19 @@
       EXPECT_FALSE(async_echo_request_callback_.is_null());
       std::move(async_echo_request_callback_).Run(async_echo_request_value_);
 
-      this->impl_->binding()->Close();
+      this->impl_->receiver()->reset();
     });
 
-    this->ptr_.set_connection_error_handler(
-        base::BindOnce(&SetFlagAndRunClosure, &connection_error_dispatched_,
-                       base::BindLambdaForTesting(
-                           [this]() { OnErrorNotificationDispatched(); })));
+    this->remote_.set_disconnect_handler(base::BindLambdaForTesting([&] {
+      disconnect_dispatched_ = true;
+      OnErrorNotificationDispatched();
+    }));
 
     int32_t result_value = -1;
-    ASSERT_FALSE(this->ptr_->Echo(456, &result_value));
+    ASSERT_FALSE(this->remote_->Echo(456, &result_value));
     EXPECT_EQ(-1, result_value);
-    ASSERT_FALSE(connection_error_dispatched_);
-    EXPECT_FALSE(this->ptr_.encountered_error());
+    ASSERT_FALSE(disconnect_dispatched_);
+    EXPECT_TRUE(this->remote_.is_connected());
 
     // Although the AsyncEcho response arrives before the Echo response, it
     // should
@@ -1100,155 +1066,143 @@
   }
 
   void OnErrorNotificationDispatched() {
-    ASSERT_TRUE(connection_error_dispatched_);
-    EXPECT_TRUE(this->ptr_.encountered_error());
+    ASSERT_TRUE(disconnect_dispatched_);
+    EXPECT_FALSE(this->remote_.is_connected());
     this->Done();
   }
 
   int32_t async_echo_request_value_ = -1;
   TestSync::AsyncEchoCallback async_echo_request_callback_;
   bool async_echo_response_dispatched_ = false;
-  bool connection_error_dispatched_ = false;
+  bool disconnect_dispatched_ = false;
 };
 
 TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, an invalid incoming message will disconnect the message pipe, cause
-  // the sync call to return false, and run the connection error handler
-  // asynchronously.
+  // Test that while a remote is waiting for the response to a sync call, an
+  // invalid incoming message will disconnect the message pipe, cause the sync
+  // call to return false, and run the disconnect handler asynchronously.
 
   using Interface = typename TypeParam::Interface;
   MessagePipe pipe;
 
-  InterfacePtr<Interface> interface_ptr;
-  interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u));
-  auto ptr = TypeParam::Wrap(std::move(interface_ptr));
+  PendingRemote<Interface> remote;
+  ScopedMessagePipeHandle receiving_handle =
+      remote.InitWithNewPipeAndPassReceiver().PassPipe();
+  auto wrapped_remote = TypeParam::Wrap(std::move(remote));
 
-  MessagePipeHandle raw_binding_handle = pipe.handle1.get();
+  auto raw_receiving_handle = receiving_handle.get();
   ImplTypeFor<Interface> impl(
-      InterfaceRequest<Interface>(std::move(pipe.handle1)));
+      PendingReceiver<Interface>(std::move(receiving_handle)));
 
-  impl.set_echo_handler([&raw_binding_handle](int32_t value,
-                                              TestSync::EchoCallback callback) {
+  impl.set_echo_handler([&](int32_t value, TestSync::EchoCallback callback) {
     // Write a 1-byte message, which is considered invalid.
     char invalid_message = 0;
     MojoResult result =
-        WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr, 0u,
+        WriteMessageRaw(raw_receiving_handle, &invalid_message, 1u, nullptr, 0u,
                         MOJO_WRITE_MESSAGE_FLAG_NONE);
     ASSERT_EQ(MOJO_RESULT_OK, result);
     std::move(callback).Run(value);
   });
 
-  bool connection_error_dispatched = false;
+  bool disconnect_dispatched = false;
   base::RunLoop run_loop;
-  // ThreadSafeInterfacePtr doesn't support setting connection error handlers.
-  if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
-    ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure,
-                                                &connection_error_dispatched,
-                                                run_loop.QuitClosure()));
+  // SharedRemote doesn't support setting disconnect handlers.
+  if (!TypeParam::kIsSharedRemoteTest) {
+    wrapped_remote.set_disconnect_handler(base::BindLambdaForTesting([&] {
+      disconnect_dispatched = true;
+      run_loop.Quit();
+    }));
   }
 
   int32_t result_value = -1;
-  ASSERT_FALSE(ptr->Echo(456, &result_value));
+  ASSERT_FALSE(wrapped_remote->Echo(456, &result_value));
   EXPECT_EQ(-1, result_value);
-  ASSERT_FALSE(connection_error_dispatched);
+  ASSERT_FALSE(disconnect_dispatched);
 
-  if (!TypeParam::kIsThreadSafeInterfacePtrTest) {
+  if (!TypeParam::kIsSharedRemoteTest) {
     run_loop.Run();
-    ASSERT_TRUE(connection_error_dispatched);
+    ASSERT_TRUE(disconnect_dispatched);
   }
 }
 
 SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
                                    InvalidMessageDuringSyncCall) {
-  // Test that while an interface pointer is waiting for the response to a sync
-  // call, an invalid incoming message will disconnect the message pipe, cause
-  // the sync call to return false, and run the connection error handler
-  // asynchronously.
+  // Test that while a remote is waiting for the response to a sync call, an
+  // invalid incoming message will disconnect the message pipe and cause the
+  // sync call to return false, and run the disconnect handler asynchronously.
 
   void Run() override {
-    MessagePipe pipe;
-
-    using InterfaceType = typename TypeParam::Interface;
-    this->ptr_.Bind(
-        InterfacePtrInfo<InterfaceType>(std::move(pipe.handle0), 0u));
-
-    MessagePipeHandle raw_binding_handle = pipe.handle1.get();
-    this->impl_ = std::make_unique<ImplTypeFor<InterfaceType>>(
-        InterfaceRequest<InterfaceType>(std::move(pipe.handle1)));
+    MessagePipeHandle raw_receiving_handle =
+        this->impl_->receiver()->internal_state()->handle();
 
     this->impl_->set_echo_handler(
-        [raw_binding_handle](int32_t value, TestSync::EchoCallback callback) {
+        [&](int32_t value, TestSync::EchoCallback callback) {
           // Write a 1-byte message, which is considered invalid.
           char invalid_message = 0;
           MojoResult result =
-              WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr,
-                              0u, MOJO_WRITE_MESSAGE_FLAG_NONE);
+              WriteMessageRaw(raw_receiving_handle, &invalid_message, 1u,
+                              nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE);
           ASSERT_EQ(MOJO_RESULT_OK, result);
           std::move(callback).Run(value);
         });
 
-    this->ptr_.set_connection_error_handler(
-        base::BindLambdaForTesting([this]() {
-          connection_error_dispatched_ = true;
-          this->Done();
-        }));
+    this->remote_.set_disconnect_handler(base::BindLambdaForTesting([this]() {
+      disconnect_dispatched_ = true;
+      this->Done();
+    }));
 
     int32_t result_value = -1;
-    ASSERT_FALSE(this->ptr_->Echo(456, &result_value));
+    ASSERT_FALSE(this->remote_->Echo(456, &result_value));
     EXPECT_EQ(-1, result_value);
-    ASSERT_FALSE(connection_error_dispatched_);
+    ASSERT_FALSE(disconnect_dispatched_);
   }
-  bool connection_error_dispatched_ = false;
+  bool disconnect_dispatched_ = false;
 };
 
-TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) {
-  // Test that an interface pointer waiting for a sync call response can be
-  // reentered by an associated binding serving sync methods on the same thread.
-  // The associated binding belongs to the same MultiplexRouter as the waiting
-  // interface pointer.
+TEST_F(SyncMethodAssociatedTest,
+       ReenteredBySyncMethodAssociatedReceiverOfSameRouter) {
+  // Test that a remote waiting for a sync call response can be reentered by an
+  // associated receiver serving sync methods on the same thread. The associated
+  // receiver belongs to the same MultiplexRouter as the waiting remote.
 
-  TestSyncAssociatedImpl opposite_asso_impl(std::move(opposite_asso_request_));
-  TestSyncAssociatedPtr opposite_asso_ptr;
-  opposite_asso_ptr.Bind(std::move(opposite_asso_ptr_info_));
+  TestSyncAssociatedImpl client_impl(std::move(client_pending_sync_receiver_));
+  AssociatedRemote<TestSync> client_remote(
+      std::move(client_pending_sync_remote_));
 
   master_impl_->set_echo_handler(
-      [&opposite_asso_ptr](int32_t value,
-                           TestSyncMaster::EchoCallback callback) {
+      [&](int32_t value, TestSyncMaster::EchoCallback callback) {
         int32_t result_value = -1;
 
-        ASSERT_TRUE(opposite_asso_ptr->Echo(123, &result_value));
+        ASSERT_TRUE(client_remote->Echo(123, &result_value));
         EXPECT_EQ(123, result_value);
         std::move(callback).Run(value);
       });
 
   int32_t result_value = -1;
-  ASSERT_TRUE(master_ptr_->Echo(456, &result_value));
+  ASSERT_TRUE(master_remote_->Echo(456, &result_value));
   EXPECT_EQ(456, result_value);
 }
 
 TEST_F(SyncMethodAssociatedTest,
-       ReenteredBySyncMethodAssoBindingOfDifferentRouter) {
-  // Test that an interface pointer waiting for a sync call response can be
-  // reentered by an associated binding serving sync methods on the same thread.
-  // The associated binding belongs to a different MultiplexRouter as the
-  // waiting interface pointer.
+       ReenteredBySyncMethodAssociatedReceiverOfDifferentRouter) {
+  // Test that a remote waiting for a sync call response can be reentered by an
+  // associated receiver serving sync methods on the same thread. The associated
+  // receiver belongs to the same MultiplexRouter as the waiting remote.
 
-  TestSyncAssociatedImpl asso_impl(std::move(asso_request_));
-  TestSyncAssociatedPtr asso_ptr;
-  asso_ptr.Bind(std::move(asso_ptr_info_));
+  TestSyncAssociatedImpl impl(std::move(impl_pending_sync_receiver_));
+  AssociatedRemote<TestSync> remote(std::move(impl_pending_sync_remote_));
 
   master_impl_->set_echo_handler(
-      [&asso_ptr](int32_t value, TestSyncMaster::EchoCallback callback) {
+      [&](int32_t value, TestSyncMaster::EchoCallback callback) {
         int32_t result_value = -1;
 
-        ASSERT_TRUE(asso_ptr->Echo(123, &result_value));
+        ASSERT_TRUE(remote->Echo(123, &result_value));
         EXPECT_EQ(123, result_value);
         std::move(callback).Run(value);
       });
 
   int32_t result_value = -1;
-  ASSERT_TRUE(master_ptr_->Echo(456, &result_value));
+  ASSERT_TRUE(master_remote_->Echo(456, &result_value));
   EXPECT_EQ(456, result_value);
 }
 
diff --git a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
index 6df1e8b..f036e7d 100644
--- a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
@@ -9,24 +9,24 @@
 interface FooInterface {};
 
 struct StructContainsAssociated {
-  associated FooInterface? foo_interface;
-  associated FooInterface& foo_request;
-  array<associated FooInterface> foo_interfaces;
-  array<associated FooInterface&> foo_requests;
+  pending_associated_remote<FooInterface>? foo_remote;
+  pending_associated_receiver<FooInterface> foo_receiver;
+  array<pending_associated_remote<FooInterface>> foo_remotes;
+  array<pending_associated_receiver<FooInterface>> foo_receivers;
 };
 
 union UnionContainsAssociated {
-  associated FooInterface foo_interface;
-  associated FooInterface& foo_request;
-  array<associated FooInterface> foo_interfaces;
-  array<associated FooInterface&> foo_requests;
+  pending_associated_remote<FooInterface>? foo_remote;
+  pending_associated_receiver<FooInterface> foo_receiver;
+  array<pending_associated_remote<FooInterface>> foo_remotes;
+  array<pending_associated_receiver<FooInterface>> foo_receivers;
 };
 
 interface InterfacePassesAssociated {
-  PassFoo(associated FooInterface foo_interface,
-          associated FooInterface& foo_request) =>
-         (associated FooInterface foo_interface,
-          associated FooInterface& foo_request);
+  PassFoo(pending_associated_remote<FooInterface> foo_remote,
+          pending_associated_receiver<FooInterface> foo_receiver) =>
+         (pending_associated_remote<FooInterface> foo_remote,
+          pending_associated_receiver<FooInterface> foo_receiver);
 
   PassStruct(StructContainsAssociated foo_struct) =>
             (StructContainsAssociated foo_struct);
@@ -46,24 +46,24 @@
 };
 
 interface IntegerSenderConnection {
-  GetSender(associated IntegerSender& sender);
-  AsyncGetSender() => (associated IntegerSender sender);
+  GetSender(pending_associated_receiver<IntegerSender> receiver);
+  AsyncGetSender() => (pending_associated_remote<IntegerSender> sender);
 };
 
 interface IntegerSenderConnectionAtBothEnds {
-  GetSender(associated IntegerSender& sender);
-  SetSender(associated IntegerSender sender) => (int32 value);
+  GetSender(pending_associated_receiver<IntegerSender> receiver);
+  SetSender(pending_associated_remote<IntegerSender> sender) => (int32 value);
 };
 
 interface SenderConnection {
-  GetIntegerSender(associated IntegerSender& sender);
-  GetStringSender(associated StringSender& sender);
+  GetIntegerSender(pending_associated_receiver<IntegerSender> receiver);
+  GetStringSender(pending_associated_receiver<StringSender> receiver);
 };
 
 interface AssociatedPingProvider {
-  GetPing(associated PingService& request);
+  GetPing(pending_associated_receiver<PingService> receiver);
 };
 
 interface AssociatedPingProviderProvider {
-  GetPingProvider(associated AssociatedPingProvider& request);
+  GetPingProvider(pending_associated_receiver<AssociatedPingProvider> receiver);
 };
diff --git a/net/base/data_url.cc b/net/base/data_url.cc
index e3c0e36..495f91b 100644
--- a/net/base/data_url.cc
+++ b/net/base/data_url.cc
@@ -102,7 +102,7 @@
   // of the data, and should be stripped. Otherwise, the escaped whitespace
   // could be part of the payload, so don't strip it.
   if (base64_encoded)
-    UnescapeBinaryURLComponent(temp_data, &temp_data);
+    temp_data = UnescapeBinaryURLComponent(temp_data);
 
   // Strip whitespace.
   if (base64_encoded || !(mime_type->compare(0, 5, "text/") == 0 ||
@@ -111,7 +111,7 @@
   }
 
   if (!base64_encoded)
-    UnescapeBinaryURLComponent(temp_data, &temp_data);
+    temp_data = UnescapeBinaryURLComponent(temp_data);
 
   if (base64_encoded) {
     size_t length = temp_data.length();
diff --git a/net/base/escape.cc b/net/base/escape.cc
index b37078cb..3ddc2217e 100644
--- a/net/base/escape.cc
+++ b/net/base/escape.cc
@@ -487,48 +487,48 @@
   return base::UTF8ToUTF16WithAdjustments(text, adjustments);
 }
 
-void UnescapeBinaryURLComponent(const std::string& escaped_text,
-                                UnescapeRule::Type rules,
-                                std::string* unescaped_text) {
+std::string UnescapeBinaryURLComponent(base::StringPiece escaped_text,
+                                       UnescapeRule::Type rules) {
   // Only NORMAL and REPLACE_PLUS_WITH_SPACE are supported.
   DCHECK(rules != UnescapeRule::NONE);
   DCHECK(!(rules &
            ~(UnescapeRule::NORMAL | UnescapeRule::REPLACE_PLUS_WITH_SPACE)));
 
+  std::string unescaped_text;
+
   // The output of the unescaping is always smaller than the input, so we can
   // reserve the input size to make sure we have enough buffer and don't have
   // to allocate in the loop below.
   // Increase capacity before size, as just resizing can grow capacity
   // needlessly beyond our requested size.
-  if (unescaped_text->capacity() < escaped_text.size())
-    unescaped_text->reserve(escaped_text.size());
-  if (unescaped_text->size() < escaped_text.size())
-    unescaped_text->resize(escaped_text.size());
+  unescaped_text.reserve(escaped_text.size());
+  unescaped_text.resize(escaped_text.size());
 
   size_t output_index = 0;
 
-  for (size_t i = 0, max = unescaped_text->size(); i < max;) {
+  for (size_t i = 0, max = escaped_text.size(); i < max;) {
     unsigned char byte;
     // UnescapeUnsignedByteAtIndex does bounds checking, so this is always safe
     // to call.
     if (UnescapeUnsignedByteAtIndex(escaped_text, i, &byte)) {
-      (*unescaped_text)[output_index++] = byte;
+      unescaped_text[output_index++] = byte;
       i += 3;
       continue;
     }
 
     if ((rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE) &&
         escaped_text[i] == '+') {
-      (*unescaped_text)[output_index++] = ' ';
+      unescaped_text[output_index++] = ' ';
       ++i;
       continue;
     }
 
-    (*unescaped_text)[output_index++] = escaped_text[i++];
+    unescaped_text[output_index++] = escaped_text[i++];
   }
 
-  DCHECK_LE(output_index, unescaped_text->size());
-  unescaped_text->resize(output_index);
+  DCHECK_LE(output_index, unescaped_text.size());
+  unescaped_text.resize(output_index);
+  return unescaped_text;
 }
 
 base::string16 UnescapeForHTML(base::StringPiece16 input) {
diff --git a/net/base/escape.h b/net/base/escape.h
index 8e86e61f..6364cf1 100644
--- a/net/base/escape.h
+++ b/net/base/escape.h
@@ -149,16 +149,9 @@
 // be used when displaying the decoded data to the user.
 //
 // Only the NORMAL and REPLACE_PLUS_WITH_SPACE rules are allowed.
-// |escaped_text| and |unescaped_text| can be the same string.
-NET_EXPORT void UnescapeBinaryURLComponent(const std::string& escaped_text,
-                                           UnescapeRule::Type rules,
-                                           std::string* unescaped_text);
-NET_EXPORT inline void UnescapeBinaryURLComponent(
-    const std::string& escaped_text,
-    std::string* unescaped_text) {
-  UnescapeBinaryURLComponent(escaped_text, UnescapeRule::NORMAL,
-                             unescaped_text);
-}
+NET_EXPORT std::string UnescapeBinaryURLComponent(
+    base::StringPiece escaped_text,
+    UnescapeRule::Type rules = UnescapeRule::NORMAL);
 
 // Unescapes the following ampersand character codes from |text|:
 // &lt; &gt; &amp; &quot; &#39;
diff --git a/net/base/escape_unittest.cc b/net/base/escape_unittest.cc
index f70f7cd..3a630ea4 100644
--- a/net/base/escape_unittest.cc
+++ b/net/base/escape_unittest.cc
@@ -411,13 +411,8 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    std::string output;
-    UnescapeBinaryURLComponent(test_case.input, test_case.rules, &output);
-    EXPECT_EQ(std::string(test_case.output), output);
-    // Also test in-place unescaping.
-    output = test_case.input;
-    UnescapeBinaryURLComponent(output, test_case.rules, &output);
-    EXPECT_EQ(std::string(test_case.output), output);
+    EXPECT_EQ(test_case.output,
+              UnescapeBinaryURLComponent(test_case.input, test_case.rules));
   }
 
   // Test NULL character unescaping, which can't be tested above since those are
@@ -430,13 +425,7 @@
   expected.push_back(0);
   expected.push_back(0);
   expected.append("9Test");
-  std::string output;
-  UnescapeBinaryURLComponent(input, &output);
-  EXPECT_EQ(expected, output);
-  // Also test in-place unescaping.
-  output = input;
-  UnescapeBinaryURLComponent(output, &output);
-  EXPECT_EQ(expected, output);
+  EXPECT_EQ(expected, UnescapeBinaryURLComponent(input));
 }
 
 TEST(EscapeTest, EscapeForHTML) {
diff --git a/net/base/filename_util.cc b/net/base/filename_util.cc
index a2bfb499..a3135f1e 100644
--- a/net/base/filename_util.cc
+++ b/net/base/filename_util.cc
@@ -120,7 +120,7 @@
   // Unescape all percent-encoded sequences, including blacklisted-for-display
   // characters, control characters and invalid UTF-8 byte sequences.
   // Percent-encoded bytes are not meaningful in a file system.
-  UnescapeBinaryURLComponent(path, &path);
+  path = UnescapeBinaryURLComponent(path);
 
 #if defined(OS_WIN)
   if (base::IsStringUTF8(path)) {
diff --git a/net/base/filename_util_internal.cc b/net/base/filename_util_internal.cc
index e937a8b..46dc33a 100644
--- a/net/base/filename_util_internal.cc
+++ b/net/base/filename_util_internal.cc
@@ -125,9 +125,8 @@
   if (!url.is_valid() || url.SchemeIs("about") || url.SchemeIs("data"))
     return std::string();
 
-  std::string unescaped_url_filename;
-  UnescapeBinaryURLComponent(url.ExtractFileName(), UnescapeRule::NORMAL,
-                             &unescaped_url_filename);
+  std::string unescaped_url_filename =
+      UnescapeBinaryURLComponent(url.ExtractFileName(), UnescapeRule::NORMAL);
 
   // The URL's path should be escaped UTF-8, but may not be.
   std::string decoded_filename = unescaped_url_filename;
diff --git a/net/docs/proxy.md b/net/docs/proxy.md
new file mode 100644
index 0000000..79449e1
--- /dev/null
+++ b/net/docs/proxy.md
@@ -0,0 +1,58 @@
+# Proxy support
+
+## Implicit bypass rules
+
+Requests to certain hosts will not be sent through a proxy, and will instead be sent directly.
+
+We call these the _implicit bypass rules_. The implicit bypass rules match URLs
+whose host portion is either a localhost name or a link-local IP literal.
+Essentially it matches:
+
+```
+localhost
+*.localhost
+[::1]
+127.0.0.1/8
+169.254/16
+[FE80::]/10
+```
+
+The complete rules are slightly more complicated. For instance on
+Windows we will also recognize `loopback`, and there is special casing of
+`localhost6` and `localhost6.localdomain6` in Chrome's localhost matching.
+
+This concept of implicit proxy bypass rules is consistent with the
+platform-level proxy support on Windows and macOS (albeit with some differences
+due to their implementation quirks - see compatibility notes in
+`net::ProxyBypassRules::MatchesImplicitRules`)
+
+Why apply implicit proxy bypass rules in the first place? Certainly there are
+considerations around ergonomics and user expectation, but the bigger problem
+is security. Since the web platform treats `localhost` as a secure origin, the
+ability to proxy it grants extra powers. This is [especially
+problematic](https://bugs.chromium.org/p/chromium/issues/detail?id=899126) when
+proxy settings are externally controllable, as when using PAC scripts.
+
+Historical support in Chrome:
+
+* Prior to M71 there were no implicit proxy bypass rules (except if using
+  `--winhttp-proxy-resolver`)
+* In M71 Chrome applied implicit proxy bypass rules to PAC scripts
+* In M72 Chrome generalized the implicit proxy bypass rules to manually configured proxies
+
+## Overriding the implicit bypass rules
+
+If you want traffic to `localhost` to be sent through a proxy despite the
+security concerns, it can be done by adding the special proxy bypass rule
+`<-loopback>`. This has the effect of _subtracting_ the implicit rules.
+
+For instance, launch Chrome with the command line flag:
+
+```
+--proxy-bypass-list="<-loopback>"
+```
+
+Note that there currently is no mechanism to disable the implicit proxy bypass
+rules when using a PAC script. Proxy bypass lists only apply to manual
+settings, so the technique above cannot be used to let PAC scripts decide the
+proxy for localhost URLs.
diff --git a/net/http/http_auth_handler_digest.cc b/net/http/http_auth_handler_digest.cc
index 15e3724b..2a16e52 100644
--- a/net/http/http_auth_handler_digest.cc
+++ b/net/http/http_auth_handler_digest.cc
@@ -6,9 +6,9 @@
 
 #include <string>
 
-#include "base/hash/md5.h"
 #include "base/logging.h"
 #include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -22,6 +22,7 @@
 #include "net/http/http_auth_scheme.h"
 #include "net/http/http_request_info.h"
 #include "net/http/http_util.h"
+#include "third_party/boringssl/src/include/openssl/md5.h"
 #include "url/gurl.h"
 
 namespace net {
@@ -330,24 +331,30 @@
     const AuthCredentials& credentials,
     const std::string& cnonce,
     const std::string& nc) const {
+  const auto md5_string = [](const std::string& input) {
+    uint8_t digest[MD5_DIGEST_LENGTH];
+    MD5(reinterpret_cast<const uint8_t*>(input.data()), input.size(), digest);
+    return base::ToLowerASCII(base::HexEncode(digest, MD5_DIGEST_LENGTH));
+  };
+
   // ha1 = MD5(A1)
   // TODO(eroman): is this the right encoding?
-  std::string ha1 = base::MD5String(base::UTF16ToUTF8(credentials.username()) +
-                                    ":" + original_realm_ + ":" +
-                                    base::UTF16ToUTF8(credentials.password()));
+  std::string ha1 = md5_string(base::UTF16ToUTF8(credentials.username()) + ":" +
+                               original_realm_ + ":" +
+                               base::UTF16ToUTF8(credentials.password()));
   if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
-    ha1 = base::MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
+    ha1 = md5_string(ha1 + ":" + nonce_ + ":" + cnonce);
 
   // ha2 = MD5(A2)
   // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
-  std::string ha2 = base::MD5String(method + ":" + path);
+  std::string ha2 = md5_string(method + ":" + path);
 
   std::string nc_part;
   if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
     nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
   }
 
-  return base::MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
+  return md5_string(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
 }
 
 std::string HttpAuthHandlerDigest::AssembleCredentials(
diff --git a/net/http/http_content_disposition.cc b/net/http/http_content_disposition.cc
index bd435a6..df3f590 100644
--- a/net/http/http_content_disposition.cc
+++ b/net/http/http_content_disposition.cc
@@ -189,7 +189,7 @@
   // web browser.
 
   // What IE6/7 does: %-escaped UTF-8.
-  UnescapeBinaryURLComponent(encoded_word, UnescapeRule::NORMAL, &decoded_word);
+  decoded_word = UnescapeBinaryURLComponent(encoded_word, UnescapeRule::NORMAL);
   if (decoded_word != encoded_word)
     *parse_result_flags |= HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS;
   if (base::IsStringUTF8(decoded_word)) {
@@ -323,8 +323,8 @@
     return true;
   }
 
-  std::string unescaped;
-  UnescapeBinaryURLComponent(value, UnescapeRule::NORMAL, &unescaped);
+  std::string unescaped =
+      UnescapeBinaryURLComponent(value, UnescapeRule::NORMAL);
 
   return ConvertToUtf8AndNormalize(unescaped, charset.c_str(), decoded);
 }
diff --git a/net/http/http_vary_data.cc b/net/http/http_vary_data.cc
index 221978e..e5ce43f6 100644
--- a/net/http/http_vary_data.cc
+++ b/net/http/http_vary_data.cc
@@ -20,8 +20,8 @@
 
 bool HttpVaryData::Init(const HttpRequestInfo& request_info,
                         const HttpResponseHeaders& response_headers) {
-  base::MD5Context ctx;
-  base::MD5Init(&ctx);
+  MD5_CTX ctx;
+  MD5_Init(&ctx);
 
   is_valid_ = false;
   bool processed_header = false;
@@ -50,7 +50,7 @@
   if (!processed_header)
     return false;
 
-  base::MD5Final(&request_digest_, &ctx);
+  MD5_Final(request_digest_, &ctx);
   return is_valid_ = true;
 }
 
@@ -104,7 +104,7 @@
 // static
 void HttpVaryData::AddField(const HttpRequestInfo& request_info,
                             const std::string& request_header,
-                            base::MD5Context* ctx) {
+                            MD5_CTX* ctx) {
   std::string request_value = GetRequestValue(request_info, request_header);
 
   // Append a character that cannot appear in the request header line so that we
@@ -113,7 +113,7 @@
   // For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
   request_value.append(1, '\n');
 
-  base::MD5Update(ctx, request_value);
+  MD5_Update(ctx, request_value.c_str(), request_value.size());
 }
 
 }  // namespace net
diff --git a/net/http/http_vary_data.h b/net/http/http_vary_data.h
index 0f4ceb1..0c09116 100644
--- a/net/http/http_vary_data.h
+++ b/net/http/http_vary_data.h
@@ -5,8 +5,10 @@
 #ifndef NET_HTTP_HTTP_VARY_DATA_H_
 #define NET_HTTP_HTTP_VARY_DATA_H_
 
-#include "base/hash/md5.h"
+#include <string>
+
 #include "net/base/net_export.h"
+#include "third_party/boringssl/src/include/openssl/md5.h"
 
 namespace base {
 class Pickle;
@@ -73,10 +75,10 @@
   // Append to the MD5 context for the given request header.
   static void AddField(const HttpRequestInfo& request_info,
                        const std::string& request_header,
-                       base::MD5Context* context);
+                       MD5_CTX* context);
 
   // A digested version of the request headers corresponding to the Vary header.
-  base::MD5Digest request_digest_;
+  uint8_t request_digest_[MD5_DIGEST_LENGTH];
 
   // True when request_digest_ contains meaningful data.
   bool is_valid_;
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index 46f1ee0667..0fd1b90 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -436,40 +436,35 @@
   group->AddJob(std::move(owned_connect_job), preconnecting);
 
   int rv = connect_job->Connect();
-  if (rv == OK) {
-    LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
-    if (!preconnecting) {
-      HandOutSocket(connect_job->PassSocket(), ClientSocketHandle::UNUSED,
-                    connect_job->connect_timing(), handle, base::TimeDelta(),
-                    group, request.net_log());
-    } else {
-      AddIdleSocket(connect_job->PassSocket(), group);
-    }
-    RemoveConnectJob(connect_job, group);
-  } else if (rv == ERR_IO_PENDING) {
+  if (rv == ERR_IO_PENDING) {
     // If we didn't have any sockets in this group, set a timer for potentially
     // creating a new one.  If the SYN is lost, this backup socket may complete
     // before the slow socket, improving end user latency.
     if (connect_backup_jobs_enabled_ && was_group_empty)
       group->StartBackupJobTimer(group_id);
-  } else {
-    LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
-    std::unique_ptr<StreamSocket> error_socket;
-    if (!preconnecting) {
-      DCHECK(handle);
-      handle->SetAdditionalErrorState(connect_job);
-      error_socket = connect_job->PassSocket();
-    }
-    if (error_socket) {
-      HandOutSocket(std::move(error_socket), ClientSocketHandle::UNUSED,
-                    connect_job->connect_timing(), handle, base::TimeDelta(),
-                    group, request.net_log());
-    }
-    RemoveConnectJob(connect_job, group);
-    if (group->IsEmpty())
-      RemoveGroup(group_id);
+    return rv;
   }
 
+  LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
+  if (preconnecting) {
+    if (rv == OK)
+      AddIdleSocket(connect_job->PassSocket(), group);
+  } else {
+    DCHECK(handle);
+    if (rv != OK)
+      handle->SetAdditionalErrorState(connect_job);
+    std::unique_ptr<StreamSocket> socket = connect_job->PassSocket();
+    if (socket) {
+      HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
+                    connect_job->connect_timing(), handle,
+                    base::TimeDelta() /* idle_time */, group,
+                    request.net_log());
+    }
+  }
+  RemoveConnectJob(connect_job, group);
+  if (group->IsEmpty())
+    RemoveGroup(group_id);
+
   return rv;
 }
 
@@ -1204,105 +1199,79 @@
   DCHECK_NE(ERR_IO_PENDING, result);
   DCHECK(group_map_.find(group->group_id()) != group_map_.end());
   DCHECK_EQ(group, group_map_[group->group_id()]);
+  DCHECK(result != OK || job->socket() != nullptr);
 
-  std::unique_ptr<StreamSocket> socket = job->PassSocket();
-
-  // Copies of these are needed because |job| may be deleted before they are
-  // accessed.
-  NetLogWithSource job_log = job->net_log();
-  LoadTimingInfo::ConnectTiming connect_timing = job->connect_timing();
-
-  // Check if the ConnectJob is already bound to a Request. If so, complete the
-  // request.
-  //
-  // TODO(mmenke) this logic resembles the case where the job is assigned to a
-  // request below. Look into merging the logic.
+  // Check if the ConnectJob is already bound to a Request. If so, result is
+  // returned to that specific request.
   base::Optional<Group::BoundRequest> bound_request =
       group->FindAndRemoveBoundRequestForConnectJob(job);
+  Request* request = nullptr;
+  std::unique_ptr<Request> owned_request;
   if (bound_request) {
     --connecting_socket_count_;
-    // If the ConnectJob is from a previous generation, and the socket pools
-    // weren't flushed with an error, add the request back to the group, and
-    // kick off another request.
-    if (bound_request->generation != group->generation() &&
-        bound_request->pending_error == OK) {
+
+    // If the socket pools were previously flushed with an error, return that
+    // error to the bound request and discard the socket.
+    if (bound_request->pending_error != OK) {
+      InvokeUserCallbackLater(bound_request->request->handle(),
+                              bound_request->request->release_callback(),
+                              bound_request->pending_error,
+                              bound_request->request->socket_tag());
+      bound_request->request->net_log().EndEventWithNetErrorCode(
+          NetLogEventType::SOCKET_POOL, bound_request->pending_error);
+      OnAvailableSocketSlot(group->group_id(), group);
+      CheckForStalledSocketGroups();
+      return;
+    }
+
+    // If the ConnectJob is from a previous generation, add the request back to
+    // the group, and kick off another request. The socket will be discarded.
+    if (bound_request->generation != group->generation()) {
       group->InsertUnboundRequest(std::move(bound_request->request));
       OnAvailableSocketSlot(group->group_id(), group);
       CheckForStalledSocketGroups();
       return;
     }
 
-    bool handed_out_socket = false;
-    if (bound_request->pending_error != OK) {
-      result = bound_request->pending_error;
-    } else {
-      bound_request->request->handle()->SetAdditionalErrorState(job);
-      if (socket) {
-        handed_out_socket = true;
-        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                      connect_timing, bound_request->request->handle(),
-                      base::TimeDelta(), group,
-                      bound_request->request->net_log());
-      }
-    }
-    bound_request->request->net_log().EndEventWithNetErrorCode(
-        NetLogEventType::SOCKET_POOL, result);
-    InvokeUserCallbackLater(bound_request->request->handle(),
-                            bound_request->request->release_callback(), result,
-                            bound_request->request->socket_tag());
-    if (!handed_out_socket) {
+    request = bound_request->request.get();
+  } else {
+    // In this case, RemoveConnectJob(job, _) must be called before exiting this
+    // method. Otherwise, |job| will be leaked.
+    owned_request = group->PopNextUnboundRequest();
+    request = owned_request.get();
+
+    if (!request) {
+      if (result == OK)
+        AddIdleSocket(job->PassSocket(), group);
+      RemoveConnectJob(job, group);
       OnAvailableSocketSlot(group->group_id(), group);
       CheckForStalledSocketGroups();
+      return;
     }
-    return;
+
+    LogBoundConnectJobToRequest(job->net_log().source(), *request);
   }
 
-  // RemoveConnectJob(job, _) must be called by all branches below;
-  // otherwise, |job| will be leaked.
+  // The case where there's no request is handled above.
+  DCHECK(request);
 
-  if (result == OK) {
-    DCHECK(socket.get());
-    std::unique_ptr<Request> request = group->PopNextUnboundRequest();
+  if (result != OK)
+    request->handle()->SetAdditionalErrorState(job);
+  if (job->socket()) {
+    HandOutSocket(job->PassSocket(), ClientSocketHandle::UNUSED,
+                  job->connect_timing(), request->handle(), base::TimeDelta(),
+                  group, request->net_log());
+  }
+  request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
+                                              result);
+  InvokeUserCallbackLater(request->handle(), request->release_callback(),
+                          result, request->socket_tag());
+  if (!bound_request)
     RemoveConnectJob(job, group);
-    if (request) {
-      LogBoundConnectJobToRequest(job_log.source(), *request);
-      HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                    connect_timing, request->handle(), base::TimeDelta(), group,
-                    request->net_log());
-      request->net_log().EndEvent(NetLogEventType::SOCKET_POOL);
-      InvokeUserCallbackLater(request->handle(), request->release_callback(),
-                              result, request->socket_tag());
-    } else {
-      AddIdleSocket(std::move(socket), group);
-      OnAvailableSocketSlot(group->group_id(), group);
-      CheckForStalledSocketGroups();
-    }
-  } else {
-    // If we got a socket, it must contain error information so pass that
-    // up so that the caller can retrieve it.
-    bool handed_out_socket = false;
-    std::unique_ptr<Request> request = group->PopNextUnboundRequest();
-    if (request) {
-      LogBoundConnectJobToRequest(job_log.source(), *request);
-      request->handle()->SetAdditionalErrorState(job);
-      RemoveConnectJob(job, group);
-      if (socket.get()) {
-        handed_out_socket = true;
-        HandOutSocket(std::move(socket), ClientSocketHandle::UNUSED,
-                      connect_timing, request->handle(), base::TimeDelta(),
-                      group, request->net_log());
-      }
-      request->net_log().EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL,
-                                                  result);
-      InvokeUserCallbackLater(request->handle(), request->release_callback(),
-                              result, request->socket_tag());
-    } else {
-      RemoveConnectJob(job, group);
-    }
-    if (!handed_out_socket) {
-      OnAvailableSocketSlot(group->group_id(), group);
-      CheckForStalledSocketGroups();
-    }
+  // If no socket was handed out, there's a new socket slot available.
+  if (!request->handle()->socket()) {
+    OnAvailableSocketSlot(group->group_id(), group);
+    CheckForStalledSocketGroups();
   }
 }
 
diff --git a/net/test/embedded_test_server/default_handlers.cc b/net/test/embedded_test_server/default_handlers.cc
index 3632108..c391e9e 100644
--- a/net/test/embedded_test_server/default_handlers.cc
+++ b/net/test/embedded_test_server/default_handlers.cc
@@ -20,9 +20,9 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
-#include "base/hash/md5.h"
 #include "base/macros.h"
 #include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -36,6 +36,7 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
+#include "third_party/boringssl/src/include/openssl/md5.h"
 
 namespace net {
 namespace test_server {
@@ -257,10 +258,9 @@
   http_response->set_content_type("text/html");
   if (got_all_expected) {
     for (const auto& cookie : query_list.at("set")) {
-      std::string unescaped_cookie;
-      UnescapeBinaryURLComponent(cookie, UnescapeRule::REPLACE_PLUS_WITH_SPACE,
-                                 &unescaped_cookie);
-      http_response->AddCustomHeader("Set-Cookie", unescaped_cookie);
+      http_response->AddCustomHeader(
+          "Set-Cookie", UnescapeBinaryURLComponent(
+                            cookie, UnescapeRule::REPLACE_PLUS_WITH_SPACE));
     }
   }
 
@@ -409,12 +409,18 @@
   return std::move(http_response);
 }
 
+const std::string MD5String(const std::string& input) {
+  uint8_t digest[MD5_DIGEST_LENGTH];
+  MD5(reinterpret_cast<const uint8_t*>(input.c_str()), input.size(), digest);
+  return base::ToLowerASCII(base::HexEncode(digest, sizeof(digest)));
+}
+
 // /auth-digest
 // Performs "Digest" HTTP authentication.
 std::unique_ptr<HttpResponse> HandleAuthDigest(const HttpRequest& request) {
-  std::string nonce = base::MD5String(
+  std::string nonce = MD5String(
       base::StringPrintf("privatekey%s", request.relative_url.c_str()));
-  std::string opaque = base::MD5String("opaque");
+  std::string opaque = MD5String("opaque");
   std::string password = kDefaultPassword;
   std::string realm = kDefaultRealm;
 
@@ -453,23 +459,23 @@
     } else {
       username = auth_pairs["username"];
 
-      std::string hash1 = base::MD5String(
+      std::string hash1 = MD5String(
           base::StringPrintf("%s:%s:%s", auth_pairs["username"].c_str(),
                              realm.c_str(), password.c_str()));
-      std::string hash2 = base::MD5String(base::StringPrintf(
+      std::string hash2 = MD5String(base::StringPrintf(
           "%s:%s", request.method_string.c_str(), auth_pairs["uri"].c_str()));
 
       std::string response;
       if (auth_pairs.find("qop") != auth_pairs.end() &&
           auth_pairs.find("nc") != auth_pairs.end() &&
           auth_pairs.find("cnonce") != auth_pairs.end()) {
-        response = base::MD5String(base::StringPrintf(
+        response = MD5String(base::StringPrintf(
             "%s:%s:%s:%s:%s:%s", hash1.c_str(), nonce.c_str(),
             auth_pairs["nc"].c_str(), auth_pairs["cnonce"].c_str(),
             auth_pairs["qop"].c_str(), hash2.c_str()));
       } else {
-        response = base::MD5String(base::StringPrintf(
-            "%s:%s:%s", hash1.c_str(), nonce.c_str(), hash2.c_str()));
+        response = MD5String(base::StringPrintf("%s:%s:%s", hash1.c_str(),
+                                                nonce.c_str(), hash2.c_str()));
       }
 
       if (auth_pairs["response"] == response)
@@ -512,8 +518,7 @@
 std::unique_ptr<HttpResponse> HandleServerRedirect(HttpStatusCode redirect_code,
                                                    const HttpRequest& request) {
   GURL request_url = request.GetURL();
-  std::string dest;
-  UnescapeBinaryURLComponent(request_url.query(), &dest);
+  std::string dest = UnescapeBinaryURLComponent(request_url.query_piece());
 
   std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
   http_response->set_code(redirect_code);
@@ -533,11 +538,8 @@
   if (!ShouldHandle(request, "/cross-site"))
     return nullptr;
 
-  std::string dest_all;
-  UnescapeBinaryURLComponent(
-
-      request.relative_url.substr(std::string("/cross-site").size() + 1),
-      &dest_all);
+  std::string dest_all = UnescapeBinaryURLComponent(
+      request.relative_url.substr(std::string("/cross-site").size() + 1));
 
   std::string dest;
   size_t delimiter = dest_all.find("/");
@@ -561,8 +563,7 @@
 // Returns a meta redirect to URL.
 std::unique_ptr<HttpResponse> HandleClientRedirect(const HttpRequest& request) {
   GURL request_url = request.GetURL();
-  std::string dest;
-  UnescapeBinaryURLComponent(request_url.query(), &dest);
+  std::string dest = UnescapeBinaryURLComponent(request_url.query_piece());
 
   std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
   http_response->set_content_type("text/html");
diff --git a/net/test/embedded_test_server/request_handler_util.cc b/net/test/embedded_test_server/request_handler_util.cc
index 5e609a7..314071a 100644
--- a/net/test/embedded_test_server/request_handler_util.cc
+++ b/net/test/embedded_test_server/request_handler_util.cc
@@ -88,9 +88,8 @@
 RequestQuery ParseQuery(const GURL& url) {
   RequestQuery queries;
   for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
-    std::string unescaped_query;
-    UnescapeBinaryURLComponent(
-        it.GetKey(), UnescapeRule::REPLACE_PLUS_WITH_SPACE, &unescaped_query);
+    std::string unescaped_query = UnescapeBinaryURLComponent(
+        it.GetKey(), UnescapeRule::REPLACE_PLUS_WITH_SPACE);
     queries[unescaped_query].push_back(it.GetUnescapedValue());
   }
   return queries;
diff --git a/net/tools/cachetool/cachetool.cc b/net/tools/cachetool/cachetool.cc
index d8b4e9a..b8a6c6b 100644
--- a/net/tools/cachetool/cachetool.cc
+++ b/net/tools/cachetool/cachetool.cc
@@ -10,7 +10,6 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/format_macros.h"
-#include "base/hash/md5.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -25,6 +24,7 @@
 #include "net/http/http_cache.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_util.h"
+#include "third_party/boringssl/src/include/openssl/sha.h"
 
 using disk_cache::Backend;
 using disk_cache::Entry;
@@ -367,7 +367,7 @@
   return false;
 }
 
-std::string GetMD5ForResponseBody(disk_cache::Entry* entry) {
+std::string GetSHA256ForResponseBody(disk_cache::Entry* entry) {
   if (entry->GetDataSize(kResponseContentIndex) == 0)
     return "";
 
@@ -376,8 +376,8 @@
       base::MakeRefCounted<net::IOBufferWithSize>(kInitBufferSize);
   net::TestCompletionCallback cb;
 
-  base::MD5Context ctx;
-  base::MD5Init(&ctx);
+  SHA256_CTX ctx;
+  SHA256_Init(&ctx);
 
   int bytes_read = 0;
   while (true) {
@@ -390,13 +390,14 @@
     }
 
     if (rv == 0) {
-      base::MD5Digest digest;
-      base::MD5Final(&digest, &ctx);
-      return base::MD5DigestToBase16(digest);
+      uint8_t digest[SHA256_DIGEST_LENGTH];
+      SHA256_Final(digest, &ctx);
+      return base::HexEncode(digest, sizeof(digest));
     }
 
     bytes_read += rv;
-    MD5Update(&ctx, base::StringPiece(buffer->data(), rv));
+    auto const hash_input = base::StringPiece(buffer->data(), rv);
+    SHA256_Update(&ctx, hash_input.data(), hash_input.size());
   }
 
   NOTREACHED();
@@ -425,7 +426,7 @@
       continue;
     }
 
-    std::string hash = GetMD5ForResponseBody(entry);
+    std::string hash = GetSHA256ForResponseBody(entry);
     if (hash.empty()) {
       // Sparse entries and empty bodies are skipped.
       entry->Close();
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 17c3358..5a21ea16 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -161,6 +161,8 @@
     "gcd_rest_client.h",
     "gcd_state_updater.cc",
     "gcd_state_updater.h",
+    "heartbeat_sender.cc",
+    "heartbeat_sender.h",
     "host_attributes.cc",
     "host_attributes.h",
     "host_change_notification_listener.cc",
@@ -310,9 +312,11 @@
     "//media",
     "//remoting/base",
     "//remoting/base:authorization",
+    "//remoting/base/grpc_support",
     "//remoting/host/file_transfer:common",
     "//remoting/host/input_monitor",
     "//remoting/host/security_key",
+    "//remoting/proto/remoting/v1:directory_grpc_library",
     "//remoting/protocol",
     "//remoting/resources",
     "//services/network:network_service",
diff --git a/remoting/host/heartbeat_sender.cc b/remoting/host/heartbeat_sender.cc
new file mode 100644
index 0000000..10d3edb1
--- /dev/null
+++ b/remoting/host/heartbeat_sender.cc
@@ -0,0 +1,359 @@
+// 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 "remoting/host/heartbeat_sender.h"
+
+#include <math.h>
+
+#include <cstdint>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringize_macros.h"
+#include "base/time/time.h"
+#include "remoting/base/constants.h"
+#include "remoting/base/grpc_support/grpc_async_unary_request.h"
+#include "remoting/base/grpc_support/grpc_authenticated_executor.h"
+#include "remoting/base/grpc_support/grpc_channel.h"
+#include "remoting/base/grpc_support/grpc_util.h"
+#include "remoting/base/logging.h"
+#include "remoting/base/service_urls.h"
+#include "remoting/host/host_details.h"
+#include "remoting/proto/remoting/v1/directory_service.grpc.pb.h"
+#include "remoting/signaling/signal_strategy.h"
+#include "remoting/signaling/signaling_address.h"
+
+namespace remoting {
+
+namespace {
+
+constexpr base::TimeDelta kDefaultHeartbeatInterval =
+    base::TimeDelta::FromMinutes(5);
+constexpr base::TimeDelta kHeartbeatResponseTimeout =
+    base::TimeDelta::FromSeconds(30);
+constexpr base::TimeDelta kWaitForAllStrategiesConnectedTimeout =
+    base::TimeDelta::FromSeconds(5);
+constexpr base::TimeDelta kResendDelay = base::TimeDelta::FromSeconds(10);
+constexpr base::TimeDelta kResendDelayOnHostNotFound =
+    base::TimeDelta::FromSeconds(10);
+
+const int kMaxResendOnHostNotFoundCount = 12;  // 2 minutes (12 x 10 seconds).
+
+using HeartbeatResponseCallback =
+    base::OnceCallback<void(const grpc::Status&,
+                            const apis::v1::HeartbeatResponse&)>;
+
+}  // namespace
+
+class HeartbeatSender::HeartbeatClient final {
+ public:
+  HeartbeatClient(OAuthTokenGetter* oauth_token_getter);
+  ~HeartbeatClient();
+
+  void Heartbeat(const apis::v1::HeartbeatRequest& request,
+                 HeartbeatResponseCallback callback);
+  void CancelPendingRequests();
+
+ private:
+  using DirectoryService = apis::v1::RemotingDirectoryService;
+  std::unique_ptr<DirectoryService::Stub> directory_;
+  GrpcAuthenticatedExecutor executor_;
+  DISALLOW_COPY_AND_ASSIGN(HeartbeatClient);
+};
+
+HeartbeatSender::HeartbeatClient::HeartbeatClient(
+    OAuthTokenGetter* oauth_token_getter)
+    : executor_(oauth_token_getter) {
+  directory_ = DirectoryService::NewStub(CreateSslChannelForEndpoint(
+      ServiceUrls::GetInstance()->remoting_server_endpoint()));
+}
+
+HeartbeatSender::HeartbeatClient::~HeartbeatClient() = default;
+
+void HeartbeatSender::HeartbeatClient::Heartbeat(
+    const apis::v1::HeartbeatRequest& request,
+    HeartbeatResponseCallback callback) {
+  HOST_LOG << "Sending outgoing heartbeat:\n"
+           << "signature: " << request.signature() << "\n"
+           << "host_id: " << request.host_id() << "\n"
+           << "jabber_id: " << request.jabber_id() << "\n"
+           << "tachyon_id: " << request.tachyon_id() << "\n"
+           << "sequence_id: " << request.sequence_id() << "\n"
+           << "host_version: " << request.host_version() << "\n"
+           << "host_offline_reason: " << request.host_offline_reason() << "\n"
+           << "host_os_name: " << request.host_os_name() << "\n"
+           << "host_os_version: " << request.host_os_version() << "\n"
+           << "=========================================================";
+
+  auto client_context = std::make_unique<grpc::ClientContext>();
+  SetDeadline(client_context.get(),
+              base::Time::Now() + kHeartbeatResponseTimeout);
+  auto async_request = CreateGrpcAsyncUnaryRequest(
+      base::BindOnce(&DirectoryService::Stub::AsyncHeartbeat,
+                     base::Unretained(directory_.get())),
+      std::move(client_context), request, std::move(callback));
+  executor_.ExecuteRpc(std::move(async_request));
+}
+
+void HeartbeatSender::HeartbeatClient::CancelPendingRequests() {
+  executor_.CancelPendingRequests();
+}
+
+// end of HeartbeatSender::HeartbeatClient
+
+HeartbeatSender::HeartbeatSender(
+    base::OnceClosure on_heartbeat_successful_callback,
+    base::OnceClosure on_unknown_host_id_error,
+    const std::string& host_id,
+    SignalStrategy* xmpp_signal_strategy,
+    SignalStrategy* ftl_signal_strategy,
+    const scoped_refptr<const RsaKeyPair>& host_key_pair,
+    OAuthTokenGetter* oauth_token_getter)
+    : on_heartbeat_successful_callback_(
+          std::move(on_heartbeat_successful_callback)),
+      on_unknown_host_id_error_(std::move(on_unknown_host_id_error)),
+      host_id_(host_id),
+      xmpp_signal_strategy_(xmpp_signal_strategy),
+      ftl_signal_strategy_(ftl_signal_strategy),
+      host_key_pair_(host_key_pair),
+      client_(std::make_unique<HeartbeatClient>(oauth_token_getter)) {
+  DCHECK(xmpp_signal_strategy_);
+  DCHECK(ftl_signal_strategy_);
+  DCHECK(host_key_pair_.get());
+
+  xmpp_signal_strategy_->AddListener(this);
+  ftl_signal_strategy_->AddListener(this);
+
+  // Start heartbeats if any signaling strategy is already connected. We only
+  // need to call OnSignalStrategyStateChange() once and it will figure out the
+  // state of both strategies.
+  OnSignalStrategyStateChange(ftl_signal_strategy_->GetState());
+}
+
+HeartbeatSender::~HeartbeatSender() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  xmpp_signal_strategy_->RemoveListener(this);
+  ftl_signal_strategy_->RemoveListener(this);
+}
+
+void HeartbeatSender::SetHostOfflineReason(
+    const std::string& host_offline_reason,
+    const base::TimeDelta& timeout,
+    base::OnceCallback<void(bool success)> ack_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!host_offline_reason_ack_callback_);
+
+  host_offline_reason_ = host_offline_reason;
+  host_offline_reason_ack_callback_ = std::move(ack_callback);
+  host_offline_reason_timeout_timer_.Start(
+      FROM_HERE, timeout, this, &HeartbeatSender::OnHostOfflineReasonTimeout);
+  if (IsAnyStrategyConnected()) {
+    // Drop refresh timer and pending heartbeat (which doesn't have the offline
+    // reason) and send a new heartbeat immediately with the offline reason.
+    client_->CancelPendingRequests();
+    heartbeat_timer_.AbandonAndStop();
+
+    SendHeartbeat();
+  }
+}
+
+void HeartbeatSender::OnSignalStrategyStateChange(SignalStrategy::State state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (IsEveryStrategyConnected()) {
+    wait_for_all_strategies_connected_timeout_timer_.AbandonAndStop();
+    heartbeat_timer_.AbandonAndStop();
+    SendHeartbeat();
+  } else if (IsAnyStrategyConnected()) {
+    wait_for_all_strategies_connected_timeout_timer_.Start(
+        FROM_HERE, kWaitForAllStrategiesConnectedTimeout, this,
+        &HeartbeatSender::OnWaitForAllStrategiesConnectedTimeout);
+  } else if (IsEveryStrategyDisconnected()) {
+    wait_for_all_strategies_connected_timeout_timer_.AbandonAndStop();
+    client_->CancelPendingRequests();
+    heartbeat_timer_.AbandonAndStop();
+  }
+}
+
+bool HeartbeatSender::OnSignalStrategyIncomingStanza(
+    const jingle_xmpp::XmlElement* stanza) {
+  return false;
+}
+
+void HeartbeatSender::OnHostOfflineReasonTimeout() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(host_offline_reason_ack_callback_);
+
+  std::move(host_offline_reason_ack_callback_).Run(false);
+}
+
+void HeartbeatSender::OnHostOfflineReasonAck() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!host_offline_reason_ack_callback_) {
+    DCHECK(!host_offline_reason_timeout_timer_.IsRunning());
+    return;
+  }
+
+  DCHECK(host_offline_reason_timeout_timer_.IsRunning());
+  host_offline_reason_timeout_timer_.AbandonAndStop();
+
+  std::move(host_offline_reason_ack_callback_).Run(true);
+}
+
+void HeartbeatSender::OnWaitForAllStrategiesConnectedTimeout() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!IsEveryStrategyConnected()) {
+    LOG(WARNING) << "Timeout waiting for all strategies to be connected.";
+    SendHeartbeat();
+  }
+}
+
+void HeartbeatSender::SendHeartbeat() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (IsEveryStrategyDisconnected()) {
+    LOG(WARNING)
+        << "Not sending heartbeat because all strategies are disconnected.";
+    return;
+  }
+
+  client_->Heartbeat(
+      CreateHeartbeatRequest(),
+      base::BindOnce(&HeartbeatSender::OnResponse, base::Unretained(this)));
+  ++sequence_id_;
+}
+
+void HeartbeatSender::OnResponse(const grpc::Status& status,
+                                 const apis::v1::HeartbeatResponse& response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (status.ok() && response.result() == apis::v1::HEARTBEATRESULT_SUCCESS) {
+    heartbeat_succeeded_ = true;
+    failed_heartbeat_count_ = 0;
+
+    // Notify listener of the first successful heartbeat.
+    if (on_heartbeat_successful_callback_) {
+      std::move(on_heartbeat_successful_callback_).Run();
+    }
+
+    // Notify caller of SetHostOfflineReason that we got an ack and don't
+    // schedule another heartbeat.
+    if (!host_offline_reason_.empty()) {
+      OnHostOfflineReasonAck();
+      return;
+    }
+  } else {
+    failed_heartbeat_count_++;
+  }
+
+  if (status.error_code() == grpc::StatusCode::DEADLINE_EXCEEDED) {
+    LOG(ERROR) << "Heartbeat timed out.";
+  }
+
+  // If the host was registered immediately before it sends a heartbeat,
+  // then server-side latency may prevent the server recognizing the
+  // host ID in the heartbeat. So even if all of the first few heartbeats
+  // get a "host ID not found" error, that's not a good enough reason to
+  // exit.
+  if (status.error_code() == grpc::StatusCode::NOT_FOUND &&
+      (heartbeat_succeeded_ ||
+       (failed_heartbeat_count_ > kMaxResendOnHostNotFoundCount))) {
+    if (on_unknown_host_id_error_) {
+      std::move(on_unknown_host_id_error_).Run();
+    }
+    return;
+  }
+
+  // Calculate delay before sending the next message.
+  base::TimeDelta delay;
+  // See CoreErrorDomainTranslator.java for the mapping
+  switch (status.error_code()) {
+    case grpc::StatusCode::OK:
+      delay = base::TimeDelta::FromSeconds(response.set_interval_seconds());
+      if (delay.is_zero()) {
+        LOG(WARNING)
+            << "set_interval_seconds is not set. Using default interval.";
+        delay = kDefaultHeartbeatInterval;
+      }
+      break;
+    case grpc::StatusCode::NOT_FOUND:
+      delay = kResendDelayOnHostNotFound;
+      break;
+    // TODO(yuweih): Handle sequence ID mismatch case. This is not implemented
+    // in the server yet.
+    default:
+      delay = pow(2.0, failed_heartbeat_count_) * (1 + base::RandDouble()) *
+              kResendDelay;
+      break;
+  }
+
+  heartbeat_timer_.Start(FROM_HERE, delay, this,
+                         &HeartbeatSender::SendHeartbeat);
+}
+
+apis::v1::HeartbeatRequest HeartbeatSender::CreateHeartbeatRequest() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  apis::v1::HeartbeatRequest heartbeat;
+  if (ftl_signal_strategy_->GetState() == SignalStrategy::State::CONNECTED) {
+    heartbeat.set_tachyon_id(ftl_signal_strategy_->GetLocalAddress().jid());
+  }
+  if (xmpp_signal_strategy_->GetState() == SignalStrategy::State::CONNECTED) {
+    heartbeat.set_jabber_id(xmpp_signal_strategy_->GetLocalAddress().jid());
+  }
+  heartbeat.set_host_id(host_id_);
+  heartbeat.set_sequence_id(sequence_id_);
+  if (!host_offline_reason_.empty()) {
+    heartbeat.set_host_offline_reason(host_offline_reason_);
+  }
+  heartbeat.set_signature(CreateSignature());
+  // Append host version.
+  heartbeat.set_host_version(STRINGIZE(VERSION));
+  // If we have not recorded a heartbeat success, continue sending host OS info.
+  if (!heartbeat_succeeded_) {
+    // Append host OS name.
+    heartbeat.set_host_os_name(GetHostOperatingSystemName());
+    // Append host OS version.
+    heartbeat.set_host_os_version(GetHostOperatingSystemVersion());
+  }
+  return heartbeat;
+}
+
+std::string HeartbeatSender::CreateSignature() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // TODO(yuweih): Rename JID to something FTL specific.
+  std::string jid = ftl_signal_strategy_->GetLocalAddress().jid();
+  if (jid.empty()) {
+    jid = xmpp_signal_strategy_->GetLocalAddress().jid();
+  }
+  std::string message = jid + ' ' + base::NumberToString(sequence_id_);
+  return host_key_pair_->SignMessage(message);
+}
+
+bool HeartbeatSender::IsAnyStrategyConnected() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return xmpp_signal_strategy_->GetState() ==
+             SignalStrategy::State::CONNECTED ||
+         ftl_signal_strategy_->GetState() == SignalStrategy::State::CONNECTED;
+}
+
+bool HeartbeatSender::IsEveryStrategyConnected() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return xmpp_signal_strategy_->GetState() ==
+             SignalStrategy::State::CONNECTED &&
+         ftl_signal_strategy_->GetState() == SignalStrategy::State::CONNECTED;
+}
+
+bool HeartbeatSender::IsEveryStrategyDisconnected() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return xmpp_signal_strategy_->GetState() ==
+             SignalStrategy::State::DISCONNECTED &&
+         ftl_signal_strategy_->GetState() ==
+             SignalStrategy::State::DISCONNECTED;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/heartbeat_sender.h b/remoting/host/heartbeat_sender.h
new file mode 100644
index 0000000..a30b25f
--- /dev/null
+++ b/remoting/host/heartbeat_sender.h
@@ -0,0 +1,151 @@
+// 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 REMOTING_HOST_HEARTBEAT_SENDER_H_
+#define REMOTING_HOST_HEARTBEAT_SENDER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
+#include "remoting/base/rsa_key_pair.h"
+#include "remoting/proto/remoting/v1/directory_messages.pb.h"
+#include "remoting/signaling/signal_strategy.h"
+
+namespace base {
+class TimeDelta;
+}  // namespace base
+
+namespace grpc {
+class Status;
+}  // namespace grpc
+
+namespace remoting {
+
+class OAuthTokenGetter;
+class RsaKeyPair;
+
+// HeartbeatSender periodically sends heartbeat to the directory service. See
+// the HeartbeatRequest message in directory_messages.proto for more details.
+//
+// Normally the heartbeat indicates that the host is healthy and ready to
+// accept new connections from a client, but the message can optionally include
+// a host_offline_reason field, which indicates that the host cannot accept
+// connections from the client (and might possibly be shutting down).  The value
+// of the host_offline_reason field can be either a string from
+// host_exit_codes.cc (i.e. "INVALID_HOST_CONFIGURATION" string) or one of
+// kHostOfflineReasonXxx constants (i.e. "POLICY_READ_ERROR" string).
+//
+// The sequence_id field of the heartbeat is a zero-based incrementally
+// increasing integer unique to each heartbeat from a single host.
+// The server checks the value, and if it is incorrect, includes the
+// correct value in the result stanza. The host should then send another
+// heartbeat, with the correct sequence-id, and increment the sequence-id in
+// susbequent heartbeats.
+// The signature is a base-64 encoded SHA-1 hash, signed with the host's
+// private RSA key. The message being signed is the full Jid concatenated with
+// the sequence-id, separated by one space. For example, for the heartbeat
+// stanza above, the message that is signed is
+// "user@gmail.com/chromoting_ftl_abc123 456".
+//
+// The sends a HeartbeatResponse in response to each successful heartbeat.
+class HeartbeatSender final : public SignalStrategy::Listener {
+ public:
+  // Signal strategies and |oauth_token_getter| must outlive this object.
+  // Heartbeats will start when either both of the signal strategies enter the
+  // CONNECTED state, or one of the strategy has been in CONNECTED state for
+  // a specific time interval.
+  //
+  // |on_heartbeat_successful_callback| is invoked after the first successful
+  // heartbeat.
+  //
+  // |on_unknown_host_id_error| is invoked when the host ID is permanently not
+  // recognized by the server.
+  HeartbeatSender(base::OnceClosure on_heartbeat_successful_callback,
+                  base::OnceClosure on_unknown_host_id_error,
+                  const std::string& host_id,
+                  SignalStrategy* xmpp_signal_strategy,
+                  SignalStrategy* ftl_signal_strategy,
+                  const scoped_refptr<const RsaKeyPair>& host_key_pair,
+                  OAuthTokenGetter* oauth_token_getter);
+  ~HeartbeatSender() override;
+
+  // Sets host offline reason for future heartbeat, and initiates sending a
+  // heartbeat right away.
+  //
+  // For discussion of allowed values for |host_offline_reason| argument,
+  // please see the description in the class-level comments above.
+  //
+  // |ack_callback| will be called when the server acks receiving the
+  // |host_offline_reason| or when |timeout| is reached.
+  void SetHostOfflineReason(
+      const std::string& host_offline_reason,
+      const base::TimeDelta& timeout,
+      base::OnceCallback<void(bool success)> ack_callback);
+
+ private:
+  class HeartbeatClient;
+
+  FRIEND_TEST_ALL_PREFIXES(HeartbeatSenderTest, SetInterval);
+
+  // SignalStrategy::Listener interface.
+  void OnSignalStrategyStateChange(SignalStrategy::State state) override;
+  bool OnSignalStrategyIncomingStanza(
+      const jingle_xmpp::XmlElement* stanza) override;
+
+  void SendHeartbeat();
+  void OnResponse(const grpc::Status& status,
+                  const apis::v1::HeartbeatResponse& response);
+
+  // Handlers for host-offline-reason completion and timeout.
+  void OnHostOfflineReasonTimeout();
+  void OnHostOfflineReasonAck();
+
+  void OnWaitForAllStrategiesConnectedTimeout();
+
+  // Helper methods used by DoSendStanza() to generate heartbeat stanzas.
+  apis::v1::HeartbeatRequest CreateHeartbeatRequest();
+  std::string CreateSignature();
+
+  // Note that IsAnyStrategyConnected() is not negation of
+  // IsEveryStrategyDisconnected() because there is a "CONNECTING" state.
+  bool IsAnyStrategyConnected() const;
+  bool IsEveryStrategyConnected() const;
+  bool IsEveryStrategyDisconnected() const;
+
+  base::OnceClosure on_heartbeat_successful_callback_;
+  base::OnceClosure on_unknown_host_id_error_;
+  std::string host_id_;
+  SignalStrategy* const xmpp_signal_strategy_;
+  SignalStrategy* const ftl_signal_strategy_;
+  scoped_refptr<const RsaKeyPair> host_key_pair_;
+  std::unique_ptr<HeartbeatClient> client_;
+
+  base::OneShotTimer heartbeat_timer_;
+
+  int failed_heartbeat_count_ = 0;
+
+  int sequence_id_ = 0;
+  bool heartbeat_succeeded_ = false;
+  int timed_out_heartbeats_count_ = 0;
+
+  // Fields to send and indicate completion of sending host-offline-reason.
+  std::string host_offline_reason_;
+  base::OnceCallback<void(bool success)> host_offline_reason_ack_callback_;
+  base::OneShotTimer host_offline_reason_timeout_timer_;
+  base::OneShotTimer wait_for_all_strategies_connected_timeout_timer_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(HeartbeatSender);
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_HEARTBEAT_SENDER_H_
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index a90e4b7..046b372 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -62,6 +62,7 @@
 #include "remoting/host/ftl_signaling_connector.h"
 #include "remoting/host/gcd_rest_client.h"
 #include "remoting/host/gcd_state_updater.h"
+#include "remoting/host/heartbeat_sender.h"
 #include "remoting/host/host_change_notification_listener.h"
 #include "remoting/host/host_config.h"
 #include "remoting/host/host_event_logger.h"
@@ -435,6 +436,7 @@
   std::unique_ptr<SignalingConnector> xmpp_signaling_connector_;
   std::unique_ptr<FtlSignalingConnector> ftl_signaling_connector_;
   std::unique_ptr<XmppHeartbeatSender> xmpp_heartbeat_sender_;
+  std::unique_ptr<HeartbeatSender> heartbeat_sender_;
 #if defined(USE_GCD)
   std::unique_ptr<GcdStateUpdater> gcd_state_updater_;
   std::unique_ptr<PushNotificationSubscriber> gcd_subscriber_;
@@ -1459,6 +1461,7 @@
   DCHECK(!gcd_subscriber_);
 #endif  // defined(USE_GCD)
   DCHECK(!xmpp_heartbeat_sender_);
+  DCHECK(!heartbeat_sender_);
 
   // Create SignalStrategy.
   xmpp_signal_strategy_ = std::make_unique<XmppSignalStrategy>(
@@ -1510,11 +1513,23 @@
       new PushNotificationSubscriber(xmpp_signal_strategy_.get(), subs));
 #endif  // defined(USE_GCD)
 
-  // Create XmppHeartbeatSender.
-  xmpp_heartbeat_sender_.reset(new XmppHeartbeatSender(
-      base::Bind(&HostProcess::OnHeartbeatSuccessful, base::Unretained(this)),
-      base::Bind(&HostProcess::OnUnknownHostIdError, base::Unretained(this)),
-      host_id_, xmpp_signal_strategy_.get(), key_pair_, directory_bot_jid_));
+  // Create heartbeat sender.
+  if (enable_ftl_signaling_) {
+    heartbeat_sender_ = std::make_unique<HeartbeatSender>(
+        base::BindOnce(&HostProcess::OnHeartbeatSuccessful,
+                       base::Unretained(this)),
+        base::BindOnce(&HostProcess::OnUnknownHostIdError,
+                       base::Unretained(this)),
+        host_id_, xmpp_signal_strategy_.get(), ftl_signal_strategy_.get(),
+        key_pair_, oauth_token_getter_.get());
+  } else {
+    xmpp_heartbeat_sender_.reset(new XmppHeartbeatSender(
+        base::BindRepeating(&HostProcess::OnHeartbeatSuccessful,
+                            base::Unretained(this)),
+        base::BindRepeating(&HostProcess::OnUnknownHostIdError,
+                            base::Unretained(this)),
+        host_id_, xmpp_signal_strategy_.get(), key_pair_, directory_bot_jid_));
+  }
 }
 
 void HostProcess::StartHostIfReady() {
@@ -1705,6 +1720,12 @@
           base::TimeDelta::FromSeconds(kHostOfflineReasonTimeoutSeconds),
           base::Bind(&HostProcess::OnHostOfflineReasonAck, this));
     }
+    if (heartbeat_sender_) {
+      heartbeat_sender_->SetHostOfflineReason(
+          host_offline_reason,
+          base::TimeDelta::FromSeconds(kHostOfflineReasonTimeoutSeconds),
+          base::BindOnce(&HostProcess::OnHostOfflineReasonAck, this));
+    }
 #if defined(USE_GCD)
     if (gcd_state_updater_) {
       gcd_state_updater_->SetHostOfflineReason(
@@ -1728,6 +1749,7 @@
 
   HOST_LOG << "SendHostOfflineReason " << (success ? "succeeded." : "failed.");
   xmpp_heartbeat_sender_.reset();
+  heartbeat_sender_.reset();
   oauth_token_getter_.reset();
   ftl_signaling_connector_.reset();
   xmpp_signaling_connector_.reset();
diff --git a/remoting/proto/remoting/v1/directory_messages.proto b/remoting/proto/remoting/v1/directory_messages.proto
index 6a087d9..0208160 100644
--- a/remoting/proto/remoting/v1/directory_messages.proto
+++ b/remoting/proto/remoting/v1/directory_messages.proto
@@ -74,13 +74,14 @@
 
 // TODO(joedow): Translate XMPP stanza errors into proto format.
 // The result of a HeartBeatRequest.
+// Added HEARTBEATRESULT_ prefix because logging.h pollutes `ERROR`.
 enum HeartbeatResult {
   // Field was not set.
   HEARTBEATRESULT_UNSET = 0;
   // The heartbeat was handled successfully.
-  SUCCESS = 1;
+  HEARTBEATRESULT_SUCCESS = 1;
   // There was an error processing the heartbeat request.
-  ERROR = 2;
+  HEARTBEATRESULT_ERROR = 2;
 }
 
 // Requests an auth_code with updated OAuth scopes.  Should only be called by
diff --git a/services/device/geolocation/network_location_provider_unittest.cc b/services/device/geolocation/network_location_provider_unittest.cc
index abf6e87..e47bb6d 100644
--- a/services/device/geolocation/network_location_provider_unittest.cc
+++ b/services/device/geolocation/network_location_provider_unittest.cc
@@ -157,6 +157,19 @@
     return data;
   }
 
+  static WifiData CreateReferenceWifiScanDataWithNoMACAddress(int ap_count) {
+    WifiData data;
+    for (int i = 0; i < ap_count; ++i) {
+      AccessPointData ap;
+      ap.radio_signal_strength = ap_count - i;
+      ap.channel = IndexToChannel(i);
+      ap.signal_to_noise = i + 42;
+      ap.ssid = base::ASCIIToUTF16("Some nice+network|name\\");
+      data.access_point_data.insert(ap);
+    }
+    return data;
+  }
+
   static void CreateReferenceWifiScanDataJson(
       int ap_count,
       int start_index,
@@ -343,6 +356,24 @@
   CheckRequestIsValid(16, 4);
 }
 
+TEST_F(GeolocationNetworkProviderTest, StartProviderNoMacAddress) {
+  std::unique_ptr<LocationProvider> provider(CreateProvider(true));
+  provider->StartProvider(false);
+  // Create Wifi scan data with no MAC Addresses.
+  const int kFirstScanAps = 5;
+  wifi_data_provider_->SetData(
+      CreateReferenceWifiScanDataWithNoMACAddress(kFirstScanAps));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_EQ(1, test_url_loader_factory_.NumPending());
+
+  test_url_loader_factory_.pending_requests()->back().request.url.spec();
+
+  // Expect only 0 out of 5 original access points. since none of them have
+  // MAC Addresses.
+  CheckRequestIsValid(0, 0);
+}
+
 // Tests that the provider issues the right requests, and provides the right
 // GetPosition() results based on the responses, for the following complex
 // sequence of Wifi data situations:
diff --git a/services/device/geolocation/network_location_request.cc b/services/device/geolocation/network_location_request.cc
index 3b0c65d..ef18b20 100644
--- a/services/device/geolocation/network_location_request.cc
+++ b/services/device/geolocation/network_location_request.cc
@@ -288,7 +288,8 @@
     AddInteger("signalToNoiseRatio", ap_data->signal_to_noise, wifi_dict.get());
     wifi_access_point_list->Append(std::move(wifi_dict));
   }
-  request->Set("wifiAccessPoints", std::move(wifi_access_point_list));
+  if (!wifi_access_point_list->empty())
+    request->Set("wifiAccessPoints", std::move(wifi_access_point_list));
 }
 
 void FormatPositionError(const GURL& server_url,
diff --git a/services/network/mdns_responder.cc b/services/network/mdns_responder.cc
index a3aadf77..2b6b394 100644
--- a/services/network/mdns_responder.cc
+++ b/services/network/mdns_responder.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/guid.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "base/stl_util.h"
@@ -46,6 +47,8 @@
 // correctly implement the detection of probe queries.
 namespace {
 
+using MdnsResponderServiceError = network::MdnsResponderManager::ServiceError;
+
 // RFC 6762, Section 6.
 //
 // The multicast of responses of the same record on an interface must be at
@@ -191,6 +194,10 @@
   return query.qtype() == net::dns_protocol::kTypeANY;
 }
 
+void ReportServiceError(MdnsResponderServiceError error) {
+  UMA_HISTOGRAM_ENUMERATION("NetworkService.MdnsResponder.ServiceError", error);
+}
+
 }  // namespace
 
 namespace network {
@@ -646,7 +653,8 @@
   size_t num_started_socket_handlers = socket_handler_by_id_.size();
   if (socket_handler_by_id_.empty()) {
     start_result_ = SocketHandlerStartResult::ALL_FAILURE;
-    LOG(ERROR) << "mDNS responder manager failed to started.";
+    LOG(ERROR) << "mDNS responder manager failed to start.";
+    ReportServiceError(MdnsResponderServiceError::kFailToStartManager);
     return;
   }
 
@@ -663,6 +671,7 @@
   if (start_result_ == SocketHandlerStartResult::UNSPECIFIED ||
       start_result_ == SocketHandlerStartResult::ALL_FAILURE) {
     LOG(ERROR) << "The mDNS responder manager is not started yet.";
+    ReportServiceError(MdnsResponderServiceError::kFailToCreateResponder);
     request = nullptr;
     return;
   }
@@ -746,6 +755,7 @@
   if (socket_handler_by_id_.empty()) {
     LOG(ERROR)
         << "All socket handlers failed. Restarting the mDNS responder manager.";
+    ReportServiceError(MdnsResponderServiceError::kFatalSocketHandlerError);
     start_result_ = MdnsResponderManager::SocketHandlerStartResult::UNSPECIFIED;
     Start();
   }
@@ -810,6 +820,7 @@
   DCHECK(address.IsValid() || address.empty());
   if (!address.IsValid()) {
     LOG(ERROR) << "Invalid IP address to create a name for";
+    ReportServiceError(MdnsResponderServiceError::kInvalidIpToRegisterName);
     binding_.Close();
     manager_->OnMojoConnectionError(this);
     return;
@@ -932,6 +943,7 @@
   }
 
   LOG(ERROR) << "Received conflicting resolution for name: " << name;
+  ReportServiceError(MdnsResponderServiceError::kConflictingNameResolution);
   return true;
 }
 
diff --git a/services/network/mdns_responder.h b/services/network/mdns_responder.h
index c337667..c1b5f44 100644
--- a/services/network/mdns_responder.h
+++ b/services/network/mdns_responder.h
@@ -95,6 +95,27 @@
     virtual std::string CreateName() = 0;
   };
 
+  // Used in histograms to measure the service health.
+  enum class ServiceError {
+    // Fail to start the MdnsResponderManager after all socket handlers fail to
+    // start on each interface.
+    kFailToStartManager = 0,
+    // Fail to create a MdnsResponder after all socket handlers fail to start on
+    // each interface.
+    kFailToCreateResponder = 1,
+    // All socket handlers have encountered read errors and failed. Imminent to
+    // restart the MdnsResponderManager.
+    kFatalSocketHandlerError = 2,
+    // An invalid IP address is given to register an mDNS name for.
+    kInvalidIpToRegisterName = 3,
+    // A record is received from the network such that it resolves a name
+    // created
+    // by the service to a different address.
+    kConflictingNameResolution = 4,
+
+    kMaxValue = kConflictingNameResolution,
+  };
+
   MdnsResponderManager();
   explicit MdnsResponderManager(net::MDnsSocketFactory* socket_factory);
   ~MdnsResponderManager();
diff --git a/services/network/mdns_responder_unittest.cc b/services/network/mdns_responder_unittest.cc
index 286b767..94d3d9d 100644
--- a/services/network/mdns_responder_unittest.cc
+++ b/services/network/mdns_responder_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/connector.h"
 #include "net/base/ip_address.h"
@@ -27,12 +28,14 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace network {
+namespace {
+
 using ::testing::Invoke;
 using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::_;
-
-namespace {
+using ServiceError = MdnsResponderManager::ServiceError;
 
 const net::IPAddress kPublicAddrs[2] = {net::IPAddress(11, 11, 11, 11),
                                         net::IPAddress(22, 22, 22, 22)};
@@ -45,6 +48,11 @@
 const int kNumAnnouncementsPerInterface = 2;
 const int kNumMaxRetriesPerResponse = 2;
 
+// Keep in sync with the histogram name in ReportServiceError in
+// mdns_responder.cc
+const char kServiceErrorHistogram[] =
+    "NetworkService.MdnsResponder.ServiceError";
+
 std::string CreateMdnsQuery(uint16_t query_id,
                             const std::string& dotted_name,
                             uint16_t qtype = net::dns_protocol::kTypeA) {
@@ -119,8 +127,6 @@
 
 }  // namespace
 
-namespace network {
-
 // Tests of the response creation helpers. For positive responses, we have the
 // address records in the Answer section and, if TTL is nonzero, the
 // corresponding NSEC records in the Additional section. For negative responses,
@@ -542,6 +548,7 @@
 // an invalid IP address is given to create a name for.
 TEST_F(MdnsResponderTest,
        HostClosesMojoConnectionWhenCreatingNameForInvalidAddress) {
+  base::HistogramTester tester;
   const net::IPAddress addr;
   ASSERT_TRUE(!addr.IsValid());
   EXPECT_TRUE(client_[0].is_bound());
@@ -549,6 +556,10 @@
   EXPECT_CALL(socket_factory_, OnSendTo(_)).Times(0);
   CreateNameForAddress(0, addr);
   EXPECT_FALSE(client_[0].is_bound());
+
+  tester.ExpectBucketCount(kServiceErrorHistogram,
+                           ServiceError::kInvalidIpToRegisterName, 1);
+  tester.ExpectTotalCount(kServiceErrorHistogram, 1);
 }
 
 // Test that the responder manager closes the connection after observing
@@ -612,12 +623,19 @@
 // Test that the host generates a Mojo connection error when no socket handler
 // is successfully started.
 TEST_F(MdnsResponderTest, ClosesBindingWhenNoSocketHanlderStarted) {
+  base::HistogramTester tester;
   EXPECT_CALL(failing_socket_factory_, CreateSockets(_)).WillOnce(Return());
   Reset(true /* use_failing_socket_factory */);
   RunUntilNoTasksRemain();
   // MdnsResponderTest::OnMojoConnectionError.
   EXPECT_FALSE(client_[0].is_bound());
   EXPECT_FALSE(client_[1].is_bound());
+
+  tester.ExpectBucketCount(kServiceErrorHistogram,
+                           ServiceError::kFailToStartManager, 1);
+  tester.ExpectBucketCount(kServiceErrorHistogram,
+                           ServiceError::kFailToCreateResponder, 2);
+  tester.ExpectTotalCount(kServiceErrorHistogram, 3);
 }
 
 // Test that an announcement is retried after send failure.
@@ -967,6 +985,7 @@
 
 // Test that if all socket handlers fail to read, the manager restarts itself.
 TEST_F(MdnsResponderTest, ManagerCanRestartAfterAllSocketHandlersFailToRead) {
+  base::HistogramTester tester;
   auto create_read_failing_socket =
       [this](std::vector<std::unique_ptr<net::DatagramServerSocket>>* sockets) {
         auto socket =
@@ -983,8 +1002,15 @@
   EXPECT_CALL(failing_socket_factory_, CreateSockets(_))
       .WillOnce(Invoke(create_read_failing_socket));
   Reset(true /* use_failing_socket_factory */);
+  // Called when the manager restarts. The mocked CreateSockets() by default
+  // returns an empty vector of sockets, thus failing the restart again.
   EXPECT_CALL(failing_socket_factory_, CreateSockets(_)).Times(1);
   RunUntilNoTasksRemain();
+  tester.ExpectBucketCount(kServiceErrorHistogram,
+                           ServiceError::kFatalSocketHandlerError, 1);
+  tester.ExpectBucketCount(kServiceErrorHistogram,
+                           ServiceError::kFailToStartManager, 1);
+  tester.ExpectTotalCount(kServiceErrorHistogram, 2);
 }
 
 }  // namespace network
diff --git a/services/service_manager/sandbox/mac/common.sb b/services/service_manager/sandbox/mac/common.sb
index f786184..c0db1ebd 100644
--- a/services/service_manager/sandbox/mac/common.sb
+++ b/services/service_manager/sandbox/mac/common.sb
@@ -182,6 +182,7 @@
   (sysctl-name "hw.cachelinesize_compat")
   (sysctl-name "hw.cpufrequency_compat")
   (sysctl-name "hw.cputype")
+  (sysctl-name "hw.logicalcpu_max")
   (sysctl-name "hw.machine")
   (sysctl-name "hw.ncpu")
   (sysctl-name "hw.pagesize_compat")
@@ -192,6 +193,7 @@
   (sysctl-name "kern.maxfilesperproc")
   (sysctl-name "kern.osrelease")
   (sysctl-name "kern.ostype")
+  (sysctl-name "kern.osvariant_status")
   (sysctl-name "kern.osversion")
   (sysctl-name "kern.usrstack64")
   (sysctl-name "kern.version")
diff --git a/services/service_manager/sandbox/mac/renderer.sb b/services/service_manager/sandbox/mac/renderer.sb
index 6e1426d..4c7f572 100644
--- a/services/service_manager/sandbox/mac/renderer.sb
+++ b/services/service_manager/sandbox/mac/renderer.sb
@@ -4,9 +4,21 @@
 
 ; --- The contents of common.sb implicitly included here. ---
 
+; Put the denials first.
+; crbug.com/799149: These operations are allowed by default.
+(if (>= os-version 1013)
+  (deny iokit-get-properties process-info* nvram*)
+)
+
 ; Allow cf prefs to work.
 (allow user-preference-read)
 
+; process-info
+(if (>= os-version 1013)
+  (allow process-info-pidinfo)
+  (allow process-info-setcontrol (target self))
+)
+
 ; File reads.
 ; Reads from the home directory.
 (allow file-read-data
@@ -67,3 +79,16 @@
   (global-name "com.apple.lsd.mapdb")
   (global-name "com.apple.system.notification_center")  ; https://crbug.com/792217
 )
+
+; IOKit properties.
+(if (>= os-version 1013)
+  (allow iokit-get-properties
+    (iokit-property "CaseSensitive")
+    (iokit-property "Ejectable")
+    (iokit-property "Encrypted")
+    (iokit-property "IOClassNameOverride")
+    (iokit-property "IOMediaIcon")
+    (iokit-property "Protocol Characteristics")
+    (iokit-property "Removable")
+    (iokit-property "image-encrypted")
+))
diff --git a/services/tracing/public/cpp/perfetto/task_runner.h b/services/tracing/public/cpp/perfetto/task_runner.h
index 4ae350e3..76e91ad 100644
--- a/services/tracing/public/cpp/perfetto/task_runner.h
+++ b/services/tracing/public/cpp/perfetto/task_runner.h
@@ -17,7 +17,7 @@
 
 namespace tracing {
 
-class ScopedPerfettoPostTaskBlocker {
+class COMPONENT_EXPORT(TRACING_CPP) ScopedPerfettoPostTaskBlocker {
  public:
   explicit ScopedPerfettoPostTaskBlocker(bool enable);
   ~ScopedPerfettoPostTaskBlocker();
diff --git a/services/tracing/public/cpp/perfetto/task_runner_unittest.cc b/services/tracing/public/cpp/perfetto/task_runner_unittest.cc
index a26b5c11..39e8318f 100644
--- a/services/tracing/public/cpp/perfetto/task_runner_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/task_runner_unittest.cc
@@ -136,13 +136,14 @@
   base::RunLoop wait_for_tasks;
   SetTaskExpectations(wait_for_tasks.QuitClosure(), 3);
 
-  task_runner()->BlockPostTaskForThread();
   auto weak_ptr = destination()->GetWeakPtr();
-  task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(1); });
-  task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(2); });
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(0u, destination()->tasks_run());
-  task_runner()->UnblockPostTaskForThread();
+  {
+    ScopedPerfettoPostTaskBlocker block(true);
+    task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(1); });
+    task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(2); });
+    base::RunLoop().RunUntilIdle();
+    EXPECT_EQ(0u, destination()->tasks_run());
+  }
   // Posting an unblocked task should post the earlier deferred ones,
   // in the right order.
   task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(3); });
@@ -154,7 +155,7 @@
   base::RunLoop wait_for_tasks;
   SetTaskExpectations(wait_for_tasks.QuitClosure(), 3);
 
-  task_runner()->BlockPostTaskForThread();
+  ScopedPerfettoPostTaskBlocker block(true);
   auto weak_ptr = destination()->GetWeakPtr();
   task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(1); });
   task_runner()->PostTask([weak_ptr]() { weak_ptr->TestTask(2); });
diff --git a/services/ws/drag_drop_delegate.cc b/services/ws/drag_drop_delegate.cc
index 761c2d6..c0f9991 100644
--- a/services/ws/drag_drop_delegate.cc
+++ b/services/ws/drag_drop_delegate.cc
@@ -9,7 +9,6 @@
 #include "base/strings/string16.h"
 #include "mojo/public/cpp/bindings/map.h"
 #include "services/ws/window_tree.h"
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
 #include "ui/base/dragdrop/drop_target_event.h"
 #include "ui/base/dragdrop/file_info.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -17,38 +16,6 @@
 
 namespace ws {
 
-namespace {
-
-// Converts OSExchangeData into mime type data.
-using DragDataType = aura::OSExchangeDataProviderMus::Data;
-DragDataType GetDragData(const ui::OSExchangeData& data) {
-  aura::OSExchangeDataProviderMus mus_provider;
-
-  base::string16 string;
-  if (data.GetString(&string))
-    mus_provider.SetString(string);
-
-  GURL url;
-  base::string16 title;
-  if (data.GetURLAndTitle(ui::OSExchangeData::DO_NOT_CONVERT_FILENAMES, &url,
-                          &title)) {
-    mus_provider.SetURL(url, title);
-  }
-
-  std::vector<ui::FileInfo> file_names;
-  if (data.GetFilenames(&file_names))
-    mus_provider.SetFilenames(file_names);
-
-  base::string16 html;
-  GURL base_url;
-  if (data.GetHtml(&html, &base_url))
-    mus_provider.SetHtml(html, base_url);
-
-  return mus_provider.GetData();
-}
-
-}  // namespace
-
 DragDropDelegate::DragDropDelegate(WindowTree* window_tree,
                                    mojom::WindowTreeClient* window_tree_client,
                                    aura::Window* window,
@@ -114,7 +81,7 @@
   DCHECK(!in_drag_);
 
   in_drag_ = true;
-  tree_client_->OnDragDropStart(mojo::MapToFlatMap(GetDragData(event.data())));
+  tree_client_->OnDragDropStart({});
 }
 
 void DragDropDelegate::EndDrag() {
diff --git a/services/ws/drag_drop_delegate_unittest.cc b/services/ws/drag_drop_delegate_unittest.cc
index 1c52bf2..2ec6dc3 100644
--- a/services/ws/drag_drop_delegate_unittest.cc
+++ b/services/ws/drag_drop_delegate_unittest.cc
@@ -17,7 +17,6 @@
 #include "services/ws/window_service_test_setup.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/drag_drop_client.h"
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
 #include "ui/aura/window.h"
 #include "ui/base/dragdrop/drop_target_event.h"
 #include "ui/base/dragdrop/file_info.h"
@@ -86,21 +85,6 @@
     drag_data_ = nullptr;
   }
 
-  // Triggers a WindowTreeClient::OnDragDropStart and returns drag data.
-  using DragDataType = aura::OSExchangeDataProviderMus::Data;
-  DragDataType GetDragData(const ui::OSExchangeData& data) {
-    // A point in |window()|'s coordinates.
-    gfx::Point point = gfx::Rect(window()->bounds().size()).CenterPoint();
-
-    changes()->clear();
-    StartDrag(data, point);
-    EndDrag();
-
-    const Change& first_change = changes()->front();
-    DCHECK_EQ(CHANGE_TYPE_DRAG_DROP_START, first_change.type);
-    return mojo::FlatMapToMap(first_change.drag_data);
-  }
-
   aura::client::DragDropDelegate* delegate() {
     return aura::client::GetDragDropDelegate(window_);
   }
@@ -138,81 +122,6 @@
   EXPECT_FALSE(delegate());
 }
 
-// Tests that the window tree client gets correct drag data.
-TEST_F(DragDropDelegateTest, DragData) {
-  SetCanAcceptDrops(true);
-
-  // String data.
-  {
-    const base::string16 kTestString = base::ASCIIToUTF16("dragged string");
-    ui::OSExchangeData data;
-    data.SetString(kTestString);
-
-    aura::OSExchangeDataProviderMus mus_provider(GetDragData(data));
-    base::string16 dropped_string;
-    EXPECT_TRUE(mus_provider.GetString(&dropped_string));
-    EXPECT_EQ(kTestString, dropped_string);
-  }
-
-  // URL data.
-  {
-    const GURL kTestURL("http://test.com");
-    const base::string16 kTestTitle = base::ASCIIToUTF16("test title");
-    ui::OSExchangeData data;
-    data.SetURL(kTestURL, kTestTitle);
-
-    aura::OSExchangeDataProviderMus mus_provider(GetDragData(data));
-    GURL dropped_url;
-    base::string16 dropped_title;
-    EXPECT_TRUE(mus_provider.GetURLAndTitle(
-        ui::OSExchangeData::DO_NOT_CONVERT_FILENAMES, &dropped_url,
-        &dropped_title));
-    EXPECT_EQ(kTestURL, dropped_url);
-    EXPECT_EQ(kTestTitle, dropped_title);
-  }
-
-#if !defined(OS_WIN)
-  // File names.
-  // Exclude from Windows because OSExchangeDataProviderWin works in a different
-  // way and does not return the exactly the same filenames as the ones being
-  // set.
-  {
-    // No "display_name" since OSExchangeDataProviderMus does not support it.
-    const std::vector<ui::FileInfo> kTestFilenames = {
-        {base::FilePath(FILE_PATH_LITERAL("/tmp/test_file1")),
-         base::FilePath()},
-        {base::FilePath(FILE_PATH_LITERAL("/tmp/test_file2")),
-         base::FilePath()},
-    };
-    ui::OSExchangeData data;
-    data.SetFilenames(kTestFilenames);
-
-    aura::OSExchangeDataProviderMus mus_provider(GetDragData(data));
-    std::vector<ui::FileInfo> dropped_filenames;
-    EXPECT_TRUE(mus_provider.GetFilenames(&dropped_filenames));
-    EXPECT_EQ(kTestFilenames, dropped_filenames);
-  }
-#endif
-
-  // HTML
-  {
-    const GURL kTestURL("http://test.com");
-    const base::string16 kTestHtml = base::ASCIIToUTF16(
-        "<HTML>\n<BODY>\n"
-        "<b>bold.</b> <i><b>This is bold italic.</b></i>\n"
-        "</BODY>\n</HTML>");
-    ui::OSExchangeData data;
-    data.SetHtml(kTestHtml, kTestURL);
-
-    aura::OSExchangeDataProviderMus mus_provider(GetDragData(data));
-    base::string16 dropped_html;
-    GURL dropped_url;
-    EXPECT_TRUE(mus_provider.GetHtml(&dropped_html, &dropped_url));
-    EXPECT_EQ(kTestHtml, dropped_html);
-    // Not testing url because OSExchangeDataProviderMus does not support it.
-  }
-}
-
 // Tests the window tree client sequence of drag enter and exit.
 TEST_F(DragDropDelegateTest, EnterAndExit) {
   SetCanAcceptDrops(true);
diff --git a/services/ws/window_tree.cc b/services/ws/window_tree.cc
index 0f8cbfd..d43cff1 100644
--- a/services/ws/window_tree.cc
+++ b/services/ws/window_tree.cc
@@ -36,7 +36,6 @@
 #include "ui/aura/client/transient_window_client.h"
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/env.h"
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
 #include "ui/aura/mus/property_converter.h"
 #include "ui/aura/mus/property_utils.h"
 #include "ui/aura/window.h"
@@ -550,9 +549,7 @@
     return;
   }
 
-  ui::OSExchangeData data(std::make_unique<aura::OSExchangeDataProviderMus>(
-      mojo::FlatMapToMap(drag_data)));
-  data.provider().SetDragImage(drag_image, drag_image_offset);
+  ui::OSExchangeData data;
 
   window_service_->delegate()->RunDragLoop(
       source_window, data, screen_location, drag_operation,
diff --git a/storage/common/fileapi/file_system_util.cc b/storage/common/fileapi/file_system_util.cc
index 9f59377..78beb8b2 100644
--- a/storage/common/fileapi/file_system_util.cc
+++ b/storage/common/fileapi/file_system_util.cc
@@ -176,8 +176,7 @@
   if (file_system_type == kFileSystemTypeUnknown)
     return false;
 
-  std::string path;
-  net::UnescapeBinaryURLComponent(url.path(), &path);
+  std::string path = net::UnescapeBinaryURLComponent(url.path_piece());
 
   // Ensure the path is relative.
   while (!path.empty() && path[0] == '/')
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index ff188ba..7ed5f3d6 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -36,6 +36,7 @@
     data = [
       "//build/android/pylib",
       "//chrome/test/data/perf",
+      "//components/variations/service/generate_ui_string_overrider.py",
       "//testing/scripts",
       "//testing/test_env.py",
       "//testing/xvfb.py",
diff --git a/testing/android/proguard_for_test.flags b/testing/android/proguard_for_test.flags
index c843318..b59f81c 100644
--- a/testing/android/proguard_for_test.flags
+++ b/testing/android/proguard_for_test.flags
@@ -16,6 +16,12 @@
 -keep class * extends org.junit.** { *; }
 -keep @**.RunWith class * { *; }
 
+# Keep classes implementing ParameterProvider -- these will be instantiated
+# via reflection.
+-keep class * implements org.chromium.base.test.params.ParameterProvider {
+  *;
+}
+
 # Keep all classes that are in test packages. There is no benefit in testing
 # Proguarding of test classes, but this is as close as we can get to selecting
 # all classes from the test apk.
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 96228d0..e77cf7d 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4162,6 +4162,12 @@
     },
 
     'viz_chromeos_fyi_gtests': {
+       'viz_content_unittests': {
+        'args': [
+          '--enable-features=VizDisplayCompositor',
+        ],
+        'test': 'content_unittests',
+       },
        'viz_interactive_ui_tests': {
          'args': [
           '--enable-features=VizDisplayCompositor',
@@ -4181,15 +4187,6 @@
         },
         'test': 'browser_tests',
       },
-      'viz_content_unittests': {
-        'args': [
-          '--enable-features=VizDisplayCompositor',
-        ],
-        'test': 'content_unittests',
-      },
-    },
-
-    'viz_gtests': {
       'viz_content_browsertests': {
         'args': [
           '--enable-features=VizDisplayCompositor',
@@ -4199,9 +4196,6 @@
         },
         'test': 'content_browsertests',
       },
-    },
-
-    'viz_non_android_fyi_gtests': {
       'viz_content_unittests': {
         'args': [
           '--enable-features=VizDisplayCompositor',
@@ -4973,7 +4967,6 @@
       'linux_flavor_specific_chromium_gtests',
       'non_android_chromium_gtests',
       'viz_chromeos_gtests',
-      'viz_gtests',
     ],
 
     'linux_chromeos_with_non_network_service_gtests': [
@@ -4990,7 +4983,6 @@
       'non_android_chromium_gtests',
       'non_network_service_gtests',
       'viz_chromeos_gtests',
-      'viz_gtests',
     ],
 
     'linux_viz_gtests': [
@@ -5014,8 +5006,6 @@
       'network_service_gtests',
       'viz_chromeos_fyi_gtests',
       'viz_chromeos_gtests',
-      'viz_gtests',
-      'viz_non_android_fyi_gtests',
     ],
 
     'mojo_linux_gtests': [
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 39eefa27..29e1e96 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2284,6 +2284,7 @@
   kVerticalScrollbarThumbScrollingWithTouch = 2877,
   kHorizontalScrollbarThumbScrollingWithMouse = 2878,
   kHorizontalScrollbarThumbScrollingWithTouch = 2879,
+  kSMSReceiverStart = 2880,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_gesture_event.h b/third_party/blink/public/platform/web_gesture_event.h
index 615e6487..d993bbf 100644
--- a/third_party/blink/public/platform/web_gesture_event.h
+++ b/third_party/blink/public/platform/web_gesture_event.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/public/platform/web_float_size.h"
 #include "third_party/blink/public/platform/web_gesture_device.h"
 #include "third_party/blink/public/platform/web_input_event.h"
+#include "third_party/blink/public/platform/web_scroll_types.h"
 
 namespace blink {
 
@@ -19,13 +20,6 @@
 
 class WebGestureEvent : public WebInputEvent {
  public:
-  enum ScrollUnits {
-    kPrecisePixels = 0,  // generated by high precision devices.
-    kPixels,             // large pixel jump duration; should animate to delta.
-    kPage,               // page (visible viewport) based scrolling.
-    kLastScrollUnit = kPage,
-  };
-
   enum InertialPhaseState {
     kUnknownMomentumPhase = 0,  // No phase information.
     kNonMomentumPhase,          // Regular scrolling phase.
@@ -91,8 +85,8 @@
       // Initial motion that triggered the scroll.
       float delta_x_hint;
       float delta_y_hint;
-      // Default initialized to ScrollUnits::PrecisePixels.
-      ScrollUnits delta_hint_units;
+      // Default initialized to WebScrollGranularity::kScrollByPrecisePixel.
+      WebScrollGranularity delta_hint_units;
       // If true, this event will skip hit testing to find a scroll
       // target and instead just scroll the viewport.
       bool target_viewport;
@@ -119,14 +113,14 @@
       float velocity_x;
       float velocity_y;
       InertialPhaseState inertial_phase;
-      // Default initialized to ScrollUnits::PrecisePixels.
-      ScrollUnits delta_units;
+      // Default initialized to WebScrollGranularity::kScrollByPrecisePixel.
+      WebScrollGranularity delta_units;
     } scroll_update;
 
     struct {
       // The original delta units the ScrollBegin and ScrollUpdates
       // were sent as.
-      ScrollUnits delta_units;
+      WebScrollGranularity delta_units;
       // The state of inertial phase scrolling. OSX has unique phases for normal
       // and momentum scroll events. Should always be kUnknownMomentumPhase for
       // touch based input as it generates GestureFlingStart instead.
@@ -227,7 +221,7 @@
 #if INSIDE_BLINK
   BLINK_PLATFORM_EXPORT float DeltaXInRootFrame() const;
   BLINK_PLATFORM_EXPORT float DeltaYInRootFrame() const;
-  BLINK_PLATFORM_EXPORT ScrollUnits DeltaUnits() const;
+  BLINK_PLATFORM_EXPORT WebScrollGranularity DeltaUnits() const;
   BLINK_PLATFORM_EXPORT WebFloatPoint PositionInRootFrame() const;
   BLINK_PLATFORM_EXPORT InertialPhaseState InertialPhase() const;
   BLINK_PLATFORM_EXPORT bool Synthetic() const;
diff --git a/third_party/blink/public/platform/web_scroll_types.h b/third_party/blink/public/platform/web_scroll_types.h
index 6c6fdc5..c7584bd 100644
--- a/third_party/blink/public/platform/web_scroll_types.h
+++ b/third_party/blink/public/platform/web_scroll_types.h
@@ -22,13 +22,13 @@
 };
 
 enum WebScrollGranularity {
-  kScrollByLine,
-  kFirstScrollGranularity = kScrollByLine,
-  kScrollByPage,
-  kScrollByDocument,
+  kScrollByPrecisePixel = 0,
+  kFirstScrollGranularity = kScrollByPrecisePixel,
   kScrollByPixel,
-  kScrollByPrecisePixel,
-  kLastScrollGranularity = kScrollByPrecisePixel
+  kScrollByPage,
+  kScrollByLine,
+  kScrollByDocument,
+  kLastScrollGranularity = kScrollByDocument
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index ef46fe3c..c6ee6900 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -1021,11 +1021,9 @@
     case CSSSelector::kPseudoRoot:
       return element == element.GetDocument().documentElement();
     case CSSSelector::kPseudoLang: {
-      AtomicString value;
-      if (element.IsVTTElement())
-        value = ToVTTElement(element).Language();
-      else
-        value = element.ComputeInheritedLanguage();
+      auto* vtt_element = DynamicTo<VTTElement>(element);
+      AtomicString value = vtt_element ? vtt_element->Language()
+                                       : element.ComputeInheritedLanguage();
       const AtomicString& argument = selector.Argument();
       if (value.IsEmpty() ||
           !value.StartsWith(argument, kTextCaseASCIIInsensitive))
@@ -1058,10 +1056,14 @@
       if (mode_ == kResolvingStyle)
         element.GetDocument().SetContainsValidityStyleRules();
       return element.IsOutOfRange();
-    case CSSSelector::kPseudoFutureCue:
-      return element.IsVTTElement() && !ToVTTElement(element).IsPastNode();
-    case CSSSelector::kPseudoPastCue:
-      return element.IsVTTElement() && ToVTTElement(element).IsPastNode();
+    case CSSSelector::kPseudoFutureCue: {
+      auto* vtt_element = DynamicTo<VTTElement>(element);
+      return vtt_element && !vtt_element->IsPastNode();
+    }
+    case CSSSelector::kPseudoPastCue: {
+      auto* vtt_element = DynamicTo<VTTElement>(element);
+      return vtt_element && vtt_element->IsPastNode();
+    }
     case CSSSelector::kPseudoScope:
       if (!context.scope)
         return false;
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 305aa99..287fbbba 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -3109,6 +3109,12 @@
   return local_frame + layout_viewport->GetScrollOffset();
 }
 
+IntRect LocalFrameView::DocumentToFrame(const IntRect& rect_in_document) const {
+  IntRect rect_in_frame = rect_in_document;
+  rect_in_frame.SetLocation(DocumentToFrame(rect_in_document.Location()));
+  return rect_in_frame;
+}
+
 DoublePoint LocalFrameView::DocumentToFrame(
     const DoublePoint& point_in_document) const {
   ScrollableArea* layout_viewport = LayoutViewport();
@@ -3118,6 +3124,11 @@
   return point_in_document - layout_viewport->GetScrollOffset();
 }
 
+IntPoint LocalFrameView::DocumentToFrame(
+    const IntPoint& point_in_document) const {
+  return FlooredIntPoint(DocumentToFrame(DoublePoint(point_in_document)));
+}
+
 FloatPoint LocalFrameView::DocumentToFrame(
     const FloatPoint& point_in_document) const {
   return FloatPoint(DocumentToFrame(DoublePoint(point_in_document)));
@@ -3139,6 +3150,10 @@
                     rect_in_document.Size());
 }
 
+IntPoint LocalFrameView::FrameToDocument(const IntPoint& point_in_frame) const {
+  return FlooredIntPoint(FrameToDocument(LayoutPoint(point_in_frame)));
+}
+
 LayoutPoint LocalFrameView::FrameToDocument(
     const LayoutPoint& point_in_frame) const {
   ScrollableArea* layout_viewport = LayoutViewport();
@@ -3148,6 +3163,11 @@
   return point_in_frame + LayoutSize(layout_viewport->GetScrollOffset());
 }
 
+IntRect LocalFrameView::FrameToDocument(const IntRect& rect_in_frame) const {
+  return IntRect(FrameToDocument(rect_in_frame.Location()),
+                 rect_in_frame.Size());
+}
+
 LayoutRect LocalFrameView::FrameToDocument(
     const LayoutRect& rect_in_frame) const {
   return LayoutRect(FrameToDocument(rect_in_frame.Location()),
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 04b368e..99606fb 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -547,11 +547,15 @@
   IntRect RootFrameToDocument(const IntRect&);
   IntPoint RootFrameToDocument(const IntPoint&);
   FloatPoint RootFrameToDocument(const FloatPoint&);
-  DoublePoint DocumentToFrame(const DoublePoint&) const;
+  IntPoint DocumentToFrame(const IntPoint&) const;
   FloatPoint DocumentToFrame(const FloatPoint&) const;
+  DoublePoint DocumentToFrame(const DoublePoint&) const;
   LayoutPoint DocumentToFrame(const LayoutPoint&) const;
+  IntRect DocumentToFrame(const IntRect&) const;
   LayoutRect DocumentToFrame(const LayoutRect&) const;
+  IntPoint FrameToDocument(const IntPoint&) const;
   LayoutPoint FrameToDocument(const LayoutPoint&) const;
+  IntRect FrameToDocument(const IntRect&) const;
   LayoutRect FrameToDocument(const LayoutRect&) const;
 
   // Normally a LocalFrameView synchronously paints during full lifecycle
diff --git a/third_party/blink/renderer/core/frame/settings.cc b/third_party/blink/renderer/core/frame/settings.cc
index ac117465..875f17e 100644
--- a/third_party/blink/renderer/core/frame/settings.cc
+++ b/third_party/blink/renderer/core/frame/settings.cc
@@ -109,7 +109,7 @@
   force_dark_mode_ = enabled;
 
   if (force_dark_mode_) {
-    SetDarkMode(DarkMode::kInvertLightness);
+    SetDarkMode(DarkMode::kInvertLightnessLAB);
     SetDarkModeImagePolicy(DarkModeImagePolicy::kFilterSmart);
   } else {
     SetDarkMode(DarkMode::kOff);
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 6dd35ab..66f221f 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -1904,7 +1904,8 @@
   gsb.SetFrameScale(1);
   gsb.data.scroll_begin.delta_x_hint = -50;
   gsb.data.scroll_begin.delta_x_hint = -60;
-  gsb.data.scroll_begin.delta_hint_units = WebGestureEvent::kPrecisePixels;
+  gsb.data.scroll_begin.delta_hint_units =
+      ScrollGranularity::kScrollByPrecisePixel;
   GetFrame()->GetEventHandler().HandleGestureEvent(gsb);
 
   WebGestureEvent gsu(
@@ -1913,7 +1914,7 @@
   gsu.SetFrameScale(1);
   gsu.data.scroll_update.delta_x = -50;
   gsu.data.scroll_update.delta_y = -60;
-  gsu.data.scroll_update.delta_units = WebGestureEvent::kPrecisePixels;
+  gsu.data.scroll_update.delta_units = ScrollGranularity::kScrollByPrecisePixel;
   gsu.data.scroll_update.velocity_x = 1;
   gsu.data.scroll_update.velocity_y = 1;
 
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
index 88268ef..1957d11 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -216,10 +216,16 @@
 static bool ShouldAutofocusOnAttach(const HTMLFormControlElement* element) {
   if (!element->IsAutofocusable())
     return false;
-  if (element->GetDocument().IsSandboxed(WebSandboxFlags::kAutomaticFeatures)) {
-    // FIXME: This message should be moved off the console once a solution to
-    // https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
-    element->GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+
+  Document& doc = element->GetDocument();
+
+  // The rest of this function implements part of the autofocus algorithm in the
+  // spec:
+  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofocusing-a-form-control:-the-autofocus-attribute
+
+  // Step 4 of the spec algorithm above.
+  if (doc.IsSandboxed(WebSandboxFlags::kAutomaticFeatures)) {
+    doc.AddConsoleMessage(ConsoleMessage::Create(
         mojom::ConsoleMessageSource::kSecurity,
         mojom::ConsoleMessageLevel::kError,
         "Blocked autofocusing on a form control because the form's frame is "
@@ -227,6 +233,18 @@
     return false;
   }
 
+  // TODO(mustaq): Add Step 5 checks.
+
+  // Step 6 of the spec algorithm above.
+  if (!doc.IsInMainFrame() &&
+      !doc.TopFrameOrigin()->CanAccess(doc.GetSecurityOrigin())) {
+    doc.AddConsoleMessage(ConsoleMessage::Create(
+        mojom::ConsoleMessageSource::kSecurity,
+        mojom::ConsoleMessageLevel::kError,
+        "Blocked autofocusing on a form control in a cross-origin subframe."));
+    return false;
+  }
+
   return true;
 }
 
diff --git a/third_party/blink/renderer/core/html/html_slot_element.cc b/third_party/blink/renderer/core/html/html_slot_element.cc
index bca2ab0..2a5fbd2 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element.cc
@@ -414,8 +414,8 @@
       continue;
     if (node->IsElementNode())
       ToElement(node)->RecalcStyle(change);
-    else if (node->IsTextNode())
-      ToText(node)->RecalcTextStyle(change);
+    else if (auto* text_node = DynamicTo<Text>(node.Get()))
+      text_node->RecalcTextStyle(change);
   }
 }
 
diff --git a/third_party/blink/renderer/core/html/html_title_element.cc b/third_party/blink/renderer/core/html/html_title_element.cc
index f0aaa1ab..dd7a8aaa 100644
--- a/third_party/blink/renderer/core/html/html_title_element.cc
+++ b/third_party/blink/renderer/core/html/html_title_element.cc
@@ -62,8 +62,8 @@
   StringBuilder result;
 
   for (Node* n = firstChild(); n; n = n->nextSibling()) {
-    if (n->IsTextNode())
-      result.Append(ToText(n)->data());
+    if (auto* text_node = DynamicTo<Text>(n))
+      result.Append(text_node->data());
   }
 
   return result.ToString();
diff --git a/third_party/blink/renderer/core/html/parser/html_construction_site.cc b/third_party/blink/renderer/core/html/parser/html_construction_site.cc
index 77d36109..fdfd3c5 100644
--- a/third_party/blink/renderer/core/html/parser/html_construction_site.cc
+++ b/third_party/blink/renderer/core/html/parser/html_construction_site.cc
@@ -135,15 +135,13 @@
 
 static inline void ExecuteInsertTextTask(HTMLConstructionSiteTask& task) {
   DCHECK_EQ(task.operation, HTMLConstructionSiteTask::kInsertText);
-  DCHECK(task.child->IsTextNode());
 
   // Merge text nodes into previous ones if possible:
   // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#insert-a-character
-  Text* new_text = ToText(task.child.Get());
+  auto* new_text = To<Text>(task.child.Get());
   Node* previous_child = task.next_child ? task.next_child->previousSibling()
                                          : task.parent->lastChild();
-  if (previous_child && previous_child->IsTextNode()) {
-    Text* previous_text = ToText(previous_child);
+  if (auto* previous_text = DynamicTo<Text>(previous_child)) {
     unsigned length_limit = TextLengthLimitForContainer(*task.parent);
     if (previous_text->length() + new_text->length() < length_limit) {
       previous_text->ParserAppendData(new_text->data());
@@ -283,7 +281,7 @@
 
     DCHECK_GT(break_index, current_position);
     DCHECK_EQ(break_index - current_position, substring.length());
-    DCHECK_EQ(ToText(task.child.Get())->length(), substring.length());
+    DCHECK_EQ(To<Text>(task.child.Get())->length(), substring.length());
     current_position = break_index;
   }
 }
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index 7d7cc15..8252c3af 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -458,9 +458,8 @@
                                   ContainerNode* parent) {
   for (Node* node = vtt_node->firstChild(); node; node = node->nextSibling()) {
     Node* cloned_node;
-    if (node->IsVTTElement())
-      cloned_node =
-          ToVTTElement(node)->CreateEquivalentHTMLElement(GetDocument());
+    if (auto* vtt_element = DynamicTo<VTTElement>(node))
+      cloned_node = vtt_element->CreateEquivalentHTMLElement(GetDocument());
     else
       cloned_node = node->cloneNode(false);
     parent->AppendChild(cloned_node);
@@ -572,8 +571,8 @@
           DetermineDirectionality(node->nodeValue(), has_strong_directionality);
       if (has_strong_directionality)
         break;
-    } else if (node->IsVTTElement()) {
-      if (ToVTTElement(node)->WebVTTNodeType() == kVTTNodeTypeRubyText) {
+    } else if (auto* vtt_element = DynamicTo<VTTElement>(node)) {
+      if (vtt_element->WebVTTNodeType() == kVTTNodeTypeRubyText) {
         node = NodeTraversal::NextSkippingChildren(*node);
         continue;
       }
@@ -782,8 +781,8 @@
         is_past_node = false;
     }
 
-    if (child.IsVTTElement()) {
-      ToVTTElement(child).SetIsPastNode(is_past_node);
+    if (auto* child_vtt_element = DynamicTo<VTTElement>(child)) {
+      child_vtt_element->SetIsPastNode(is_past_node);
       // Make an elemenet id match a cue id for style matching purposes.
       if (!id().IsEmpty())
         ToElement(child).SetIdAttribute(id());
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_element.h b/third_party/blink/renderer/core/html/track/vtt/vtt_element.h
index e9fae7d..f27e8e70 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_element.h
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_element.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_TRACK_VTT_VTT_ELEMENT_H_
 
 #include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
@@ -84,7 +85,15 @@
   AtomicString language_;
 };
 
-DEFINE_ELEMENT_TYPE_CASTS(VTTElement, IsVTTElement());
+template <>
+inline bool IsElementOfType<const VTTElement>(const Node& node) {
+  return node.IsVTTElement();
+}
+
+template <>
+struct DowncastTraits<VTTElement> {
+  static bool AllowFrom(const Node& node) { return node.IsVTTElement(); }
+};
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc
index 34a8f46..7bc15cc5 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc
@@ -533,10 +533,10 @@
       if (node_type == kVTTNodeTypeNone)
         break;
 
-      VTTNodeType current_type =
-          current_node_->IsVTTElement()
-              ? ToVTTElement(current_node_.Get())->WebVTTNodeType()
-              : kVTTNodeTypeNone;
+      auto* curr_vtt_element = DynamicTo<VTTElement>(current_node_.Get());
+      VTTNodeType current_type = curr_vtt_element
+                                     ? curr_vtt_element->WebVTTNodeType()
+                                     : kVTTNodeTypeNone;
       // <rt> is only allowed if the current node is <ruby>.
       if (node_type == kVTTNodeTypeRubyText && current_type != kVTTNodeTypeRuby)
         break;
@@ -566,11 +566,11 @@
 
       // The only non-VTTElement would be the DocumentFragment root. (Text
       // nodes and PIs will never appear as current_node_.)
-      if (!current_node_->IsVTTElement())
+      auto* curr_vtt_element = DynamicTo<VTTElement>(current_node_.Get());
+      if (!curr_vtt_element)
         break;
 
-      VTTNodeType current_type =
-          ToVTTElement(current_node_.Get())->WebVTTNodeType();
+      VTTNodeType current_type = curr_vtt_element->WebVTTNodeType();
       bool matches_current = node_type == current_type;
       if (!matches_current) {
         // </ruby> auto-closes <rt>.
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index b84e3bd..6a7744b5 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/input/pointer_event_manager.h"
 
 #include "base/auto_reset.h"
+#include "base/metrics/field_trial_params.h"
 #include "third_party/blink/public/platform/web_touch_event.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/events/event_path.h"
@@ -25,11 +26,18 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/pointer_lock_controller.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
 namespace {
 
+// Field trial name for skipping touch filtering
+const char kSkipTouchEventFilterTrial[] = "SkipTouchEventFilter";
+const char kSkipTouchEventFilterTrialProcessParamName[] =
+    "skip_filtering_process";
+const char kSkipTouchEventFilterTrialTypeParamName[] = "type";
+
 size_t ToPointerTypeIndex(WebPointerProperties::PointerType t) {
   return static_cast<size_t>(t);
 }
@@ -69,6 +77,18 @@
       touch_event_manager_(MakeGarbageCollected<TouchEventManager>(frame)),
       mouse_event_manager_(mouse_event_manager) {
   Clear();
+  if (RuntimeEnabledFeatures::SkipTouchEventFilterEnabled() &&
+      base::GetFieldTrialParamValue(
+          kSkipTouchEventFilterTrial,
+          kSkipTouchEventFilterTrialProcessParamName) ==
+          "browser_and_renderer") {
+    skip_touch_filter_discrete_ = true;
+    if (base::GetFieldTrialParamValue(
+            kSkipTouchEventFilterTrial,
+            kSkipTouchEventFilterTrialTypeParamName) == "all") {
+      skip_touch_filter_all_ = true;
+    }
+  }
 }
 
 void PointerEventManager::Clear() {
@@ -160,8 +180,10 @@
 
   const PointerId pointer_id = pointer_event->pointerId();
   const AtomicString& event_type = pointer_event->type();
+  bool should_filter = ShouldFilterEvent(pointer_event);
 
-  if (!frame_ || !HasPointerEventListener(frame_->GetEventHandlerRegistry()))
+  if (should_filter &&
+      !HasPointerEventListener(frame_->GetEventHandlerRegistry()))
     return WebInputEventResult::kNotHandled;
 
   if (event_type == event_type_names::kPointerdown) {
@@ -172,13 +194,17 @@
     }
   }
 
-  if (!check_for_listener || target->HasEventListeners(event_type)) {
+  bool listeners_exist =
+      !check_for_listener || target->HasEventListeners(event_type);
+  if (listeners_exist) {
     UseCounter::Count(frame_->GetDocument(), WebFeature::kPointerEventDispatch);
     if (event_type == event_type_names::kPointerdown) {
       UseCounter::Count(frame_->GetDocument(),
                         WebFeature::kPointerEventDispatchPointerDown);
     }
+  }
 
+  if (!should_filter || listeners_exist) {
     DCHECK(!dispatching_pointer_id_);
     base::AutoReset<PointerId> dispatch_holder(&dispatching_pointer_id_,
                                                pointer_id);
@@ -357,6 +383,27 @@
       pointer_event.unique_touch_event_id, pointer_event.PositionInWidget());
 }
 
+bool PointerEventManager::ShouldFilterEvent(PointerEvent* pointer_event) {
+  // Filter as normal if the experiment is disabled.
+  if (!skip_touch_filter_discrete_)
+    return true;
+
+  // If the experiment is enabled and the event is pointer up/down, do not
+  // filter.
+  if (pointer_event->type() == event_type_names::kPointerdown ||
+      pointer_event->type() == event_type_names::kPointerup) {
+    return false;
+  }
+  // If the experiment is "all", do not filter pointermove.
+  if (skip_touch_filter_all_ &&
+      pointer_event->type() == event_type_names::kPointermove)
+    return false;
+
+  // Continue filtering other types of events, even thought the experiment is
+  // enabled.
+  return true;
+}
+
 event_handling_util::PointerEventTarget
 PointerEventManager::ComputePointerEventTarget(
     const WebPointerEvent& web_pointer_event) {
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.h b/third_party/blink/renderer/core/input/pointer_event_manager.h
index 157e02e5..63b7376 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.h
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.h
@@ -228,6 +228,10 @@
   // Adjust coordinates so it can be used to find the best clickable target.
   void AdjustTouchPointerEvent(WebPointerEvent&);
 
+  // Check if the SkipTouchEventFilter experiment is configured to skip
+  // filtering on the given event.
+  bool ShouldFilterEvent(PointerEvent* pointer_event);
+
   // NOTE: If adding a new field to this class please ensure that it is
   // cleared in |PointerEventManager::clear()|.
 
@@ -271,6 +275,12 @@
   // frame or 0 if none.
   PointerId dispatching_pointer_id_;
 
+  // These flags are set for the SkipTouchEventFilter experiment. The
+  // experiment either skips filtering discrete (touch start/end) events to the
+  // main thread, or all events (touch start/end/move).
+  bool skip_touch_filter_discrete_ = false;
+  bool skip_touch_filter_all_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(PointerEventManager);
 };
 
diff --git a/third_party/blink/renderer/core/input/scroll_manager.cc b/third_party/blink/renderer/core/input/scroll_manager.cc
index a3c597b..6b921db 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.cc
+++ b/third_party/blink/renderer/core/input/scroll_manager.cc
@@ -290,17 +290,17 @@
     // direction and end position. Pressing the Home/End key is considered as a
     // scroll with intended end position only.
     switch (granularity) {
-      case kScrollByLine: {
+      case ScrollGranularity::kScrollByLine: {
         if (snap_coordinator->SnapForDirection(*box, delta))
           return true;
         break;
       }
-      case kScrollByPage: {
+      case ScrollGranularity::kScrollByPage: {
         if (snap_coordinator->SnapForEndAndDirection(*box, delta))
           return true;
         break;
       }
-      case kScrollByDocument: {
+      case ScrollGranularity::kScrollByDocument: {
         FloatPoint end_position = scrollable_area->ScrollPosition() + delta;
         bool scrolled_x = physical_direction == kScrollLeft ||
                           physical_direction == kScrollRight;
@@ -562,8 +562,8 @@
       std::make_unique<ScrollStateData>();
   scroll_state_data->delta_x = delta.Width();
   scroll_state_data->delta_y = delta.Height();
-  scroll_state_data->delta_granularity = static_cast<double>(
-      ToPlatformScrollGranularity(gesture_event.DeltaUnits()));
+  scroll_state_data->delta_granularity =
+      static_cast<double>(gesture_event.DeltaUnits());
   scroll_state_data->velocity_x = velocity.Width();
   scroll_state_data->velocity_y = velocity.Height();
   scroll_state_data->position_x = position.X();
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index 129b4bb9..7ec58fb8 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -925,7 +925,7 @@
   if (node->getNodeType() != Node::kTextNode)
     return Response::Error("Can only set value of text nodes");
 
-  return dom_editor_->ReplaceWholeText(ToText(node), value);
+  return dom_editor_->ReplaceWholeText(To<Text>(node), value);
 }
 
 static Node* NextNodeWithShadowDOMInMind(const Node& current,
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.cc b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
index d6ee587..d59e4f71 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
@@ -425,10 +425,11 @@
     scale_ = 1.f / frame_view->GetChromeClient()->WindowToViewportScalar(1.f);
   AppendPathsForShapeOutside(node, highlight_config);
   AppendNodeHighlight(node, highlight_config);
+  auto* text_node = DynamicTo<Text>(node);
   if (append_element_info && node->IsElementNode())
     element_info_ = BuildElementInfo(ToElement(node));
-  else if (append_element_info && node->IsTextNode())
-    element_info_ = BuildTextNodeInfo(ToText(node));
+  else if (append_element_info && text_node)
+    element_info_ = BuildTextNodeInfo(text_node);
   if (element_info_ && highlight_config.show_styles)
     AppendStyleInfo(node, element_info_.get(), node_contrast);
 
diff --git a/third_party/blink/renderer/core/inspector/network_resources_data.cc b/third_party/blink/renderer/core/inspector/network_resources_data.cc
index 14a3a4cc95..a6aee756d 100644
--- a/third_party/blink/renderer/core/inspector/network_resources_data.cc
+++ b/third_party/blink/renderer/core/inspector/network_resources_data.cc
@@ -123,6 +123,12 @@
     result += post_data_->SizeInBytes();
     post_data_ = nullptr;
   }
+
+  if (xhr_replay_data_ && xhr_replay_data_->FormData()) {
+    result += xhr_replay_data_->FormData()->SizeInBytes();
+    xhr_replay_data_->DeleteFormData();
+  }
+
   return result;
 }
 
@@ -161,9 +167,17 @@
 }
 
 uint64_t NetworkResourcesData::ResourceData::DataLength() const {
-  uint64_t data_buffer_size = data_buffer_ ? data_buffer_->size() : 0;
-  uint64_t post_data_size = post_data_ ? post_data_->SizeInBytes() : 0;
-  return data_buffer_size + post_data_size;
+  uint64_t data_length = 0;
+  if (data_buffer_)
+    data_length += data_buffer_->size();
+
+  if (post_data_)
+    data_length += post_data_->SizeInBytes();
+
+  if (xhr_replay_data_ && xhr_replay_data_->FormData())
+    data_length += xhr_replay_data_->FormData()->SizeInBytes();
+
+  return data_length;
 }
 
 void NetworkResourcesData::ResourceData::AppendData(const char* data,
@@ -360,8 +374,19 @@
 void NetworkResourcesData::SetXHRReplayData(const String& request_id,
                                             XHRReplayData* xhr_replay_data) {
   ResourceData* resource_data = ResourceDataForRequestId(request_id);
-  if (resource_data)
-    resource_data->SetXHRReplayData(xhr_replay_data);
+  if (!resource_data || resource_data->IsContentEvicted())
+    return;
+
+  if (xhr_replay_data->FormData()) {
+    if (!EnsureFreeSpace(xhr_replay_data->FormData()->SizeInBytes())) {
+      xhr_replay_data->DeleteFormData();
+    } else {
+      content_size_ += xhr_replay_data->FormData()->SizeInBytes();
+      request_ids_deque_.push_back(request_id);
+    }
+  }
+
+  resource_data->SetXHRReplayData(xhr_replay_data);
 }
 
 HeapVector<Member<NetworkResourcesData::ResourceData>>
diff --git a/third_party/blink/renderer/core/inspector/network_resources_data.h b/third_party/blink/renderer/core/inspector/network_resources_data.h
index a533843..c4945a2 100644
--- a/third_party/blink/renderer/core/inspector/network_resources_data.h
+++ b/third_party/blink/renderer/core/inspector/network_resources_data.h
@@ -79,11 +79,14 @@
     visitor->Trace(execution_context_);
   }
 
+  void DeleteFormData() { form_data_ = nullptr; }
+
  private:
   WeakMember<ExecutionContext> execution_context_;
   AtomicString method_;
   KURL url_;
   bool async_;
+  // TODO(http://crbug.com/958524): Remove form_data_ after OutOfBlinkCORS is launched.
   scoped_refptr<EncodedFormData> form_data_;
   HTTPHeaderMap headers_;
   bool include_credentials_;
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 51c0a29..17c9621 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -2340,17 +2340,18 @@
     return nullptr;
 
   NGBlockNode node(this);
-  if (!MaySkipLayout(node, *cached_layout_result, new_space,
-                     initial_fragment_geometry))
+  if (CalculateSizeBasedLayoutCacheStatus(
+          node, *cached_layout_result, new_space, initial_fragment_geometry) !=
+      NGLayoutCacheStatus::kHit)
     return nullptr;
 
-  const NGConstraintSpace& old_space =
-      cached_layout_result->GetConstraintSpaceForCaching();
-
   base::Optional<LayoutUnit> bfc_block_offset =
       cached_layout_result->BfcBlockOffset();
   LayoutUnit bfc_line_offset = new_space.BfcOffset().line_offset;
 
+  const NGConstraintSpace& old_space =
+      cached_layout_result->GetConstraintSpaceForCaching();
+
   DCHECK_EQ(old_space.BfcOffset().line_offset,
             cached_layout_result->BfcLineOffset());
 
@@ -2369,72 +2370,22 @@
   bool is_exclusion_space_equal =
       new_space.ExclusionSpace() == old_space.ExclusionSpace();
 
-  LayoutUnit old_clearance_offset = old_space.ClearanceOffset();
-  LayoutUnit new_clearance_offset = new_space.ClearanceOffset();
+  bool is_new_formatting_context =
+      cached_layout_result->PhysicalFragment()->IsBlockFormattingContextRoot();
 
-  // If anything changes within the layout regarding floats, we need to perform
-  // a series of additional checks to see if the result can still be reused.
-  if (!is_bfc_offset_equal || !is_exclusion_space_equal ||
-      new_clearance_offset != old_clearance_offset) {
-    // Determine if we can reuse a result if it was affected by clearance.
-    bool is_pushed_by_floats = cached_layout_result->IsPushedByFloats();
-    if (is_pushed_by_floats) {
-      DCHECK(old_space.HasFloats());
+  // If a node *doesn't* establish a new formatting context it may be affected
+  // by floats, or clearance.
+  // If anything has changed prior to us (different exclusion space, etc), we
+  // need to perform a series of additional checks if we can still reuse this
+  // layout result.
+  if (!is_new_formatting_context &&
+      (!is_bfc_offset_equal || !is_exclusion_space_equal ||
+       new_space.ClearanceOffset() != old_space.ClearanceOffset())) {
+    DCHECK(!CreatesNewFormattingContext());
 
-      // We don't attempt to reuse the cached result if the clearance offset
-      // differs from the final BFC-block-offset.
-      //
-      // The |is_pushed_by_floats| flag is also used by nodes who have a
-      // *child* which was pushed by floats. In this case the node may not have
-      // a BFC-block-offset or one equal to the clearance offset.
-      if (!cached_layout_result->BfcBlockOffset() ||
-          *cached_layout_result->BfcBlockOffset() !=
-              old_space.ClearanceOffset())
-        return nullptr;
-
-      // We only reuse the cached result if the delta between the
-      // BFC-block-offset, and the clearance offset grows or remains the same.
-      // If it shrinks it may not be affected by clearance anymore as a margin
-      // may push the fragment below the clearance offset instead.
-      //
-      // TODO(layout-dev): If we track if any margins affected this calculation
-      // (with an additional bit on the layout result) we could potentially
-      // skip this check.
-      if (old_clearance_offset - old_space.BfcOffset().block_offset >
-          new_clearance_offset - new_space.BfcOffset().block_offset) {
-        return nullptr;
-      }
-    }
-
-    // Check we have a descendant that *may* be positioned above the block-start
-    // edge. We abort if either the old or new space has floats, as we don't
-    // keep track of how far above the child could be. This case is relatively
-    // rare, and only occurs with negative margins.
-    if (cached_layout_result->MayHaveDescendantAboveBlockStart() &&
-        (old_space.HasFloats() || new_space.HasFloats()))
+    if (!MaySkipLayoutWithinBlockFormattingContext(
+            *cached_layout_result, new_space, &bfc_block_offset))
       return nullptr;
-
-    // We can now try to adjust the BFC block-offset.
-    if (bfc_block_offset) {
-      // Check if the previous position may intersect with any floats.
-      if (*bfc_block_offset <
-          old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
-        return nullptr;
-
-      if (is_pushed_by_floats) {
-        DCHECK_EQ(*bfc_block_offset, old_clearance_offset);
-        bfc_block_offset = new_clearance_offset;
-      } else {
-        bfc_block_offset = *bfc_block_offset -
-                           old_space.BfcOffset().block_offset +
-                           new_space.BfcOffset().block_offset;
-      }
-
-      // Check if the new position may intersect with any floats.
-      if (*bfc_block_offset <
-          new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
-        return nullptr;
-    }
   }
 
   // We can safely re-use this fragment if we are positioned, and only our
diff --git a/third_party/blink/renderer/core/layout/layout_object_test.cc b/third_party/blink/renderer/core/layout/layout_object_test.cc
index b7ef26d..27c451c 100644
--- a/third_party/blink/renderer/core/layout/layout_object_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_test.cc
@@ -486,7 +486,7 @@
   Node* sample = GetDocument().getElementById("sample");
   Node* first_letter = sample->firstChild();
   // Split "abc" into "a" "bc"
-  ToText(first_letter)->splitText(1, ASSERT_NO_EXCEPTION);
+  To<Text>(first_letter)->splitText(1, ASSERT_NO_EXCEPTION);
   UpdateAllLifecyclePhasesForTest();
 
   const LayoutTextFragment* layout_object0 =
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index 481f6af..5c9aff8e 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -349,8 +349,8 @@
 }
 
 scoped_refptr<StringImpl> LayoutText::OriginalText() const {
-  Node* e = GetNode();
-  return (e && e->IsTextNode()) ? ToText(e)->DataImpl() : nullptr;
+  auto* text_node = DynamicTo<Text>(GetNode());
+  return text_node ? text_node->DataImpl() : nullptr;
 }
 
 String LayoutText::PlainText() const {
@@ -2148,14 +2148,14 @@
   const Node* node = GetNode();
   if (!node)
     return Position();
-  DCHECK(node->IsTextNode());
+  auto* text_node = To<Text>(node);
   // TODO(layout-dev): Support offset change due to text-transform.
 #if DCHECK_IS_ON()
   // Ensures that the clamping hack kicks in only with text-transform.
   if (StyleRef().TextTransform() == ETextTransform::kNone)
-    DCHECK_LE(offset, ToText(node)->length());
+    DCHECK_LE(offset, text_node->length());
 #endif
-  const unsigned clamped_offset = std::min(offset, ToText(node)->length());
+  const unsigned clamped_offset = std::min(offset, text_node->length());
   return Position(node, clamped_offset);
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_text_fragment.cc b/third_party/blink/renderer/core/layout/layout_text_fragment.cc
index 5564cc1b..834270f 100644
--- a/third_party/blink/renderer/core/layout/layout_text_fragment.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_fragment.cc
@@ -170,7 +170,7 @@
       return nullptr;
     node = next_layout_object->GetNode();
   }
-  return (node && node->IsTextNode()) ? ToText(node) : nullptr;
+  return DynamicTo<Text>(node);
 }
 
 LayoutText* LayoutTextFragment::GetFirstLetterPart() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index acc0c5c..442409ea 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -546,7 +546,7 @@
 
   Element* container = GetElementById("container");
   Element* parent = GetElementById("parent");
-  Text* text = ToText(parent->firstChild());
+  auto* text = To<Text>(parent->firstChild());
   EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines());
   EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines());
   EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines());
@@ -630,7 +630,7 @@
 
   Element* container = GetElementById("container");
   Element* parent = GetElementById("parent");
-  Text* text = ToText(parent->firstChild());
+  auto* text = To<Text>(parent->firstChild());
   EXPECT_FALSE(text->GetLayoutObject()->NeedsCollectInlines());
   EXPECT_FALSE(parent->GetLayoutObject()->NeedsCollectInlines());
   EXPECT_FALSE(container->GetLayoutObject()->NeedsCollectInlines());
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
index fab78c10..9fcf8f39 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
@@ -27,16 +27,16 @@
 }
 
 Position CreatePositionForOffsetMapping(const Node& node, unsigned dom_offset) {
-  if (node.IsTextNode()) {
+  if (auto* text_node = DynamicTo<Text>(node)) {
     // 'text-transform' may make the rendered text length longer than the
     // original text node, in which case we clamp the offset to avoid crashing.
     // TODO(crbug.com/750990): Support 'text-transform' to remove this hack.
 #if DCHECK_IS_ON()
     // Ensures that the clamping hack kicks in only with text-transform.
     if (node.ComputedStyleRef().TextTransform() == ETextTransform::kNone)
-      DCHECK_LE(dom_offset, ToText(node).length());
+      DCHECK_LE(dom_offset, text_node->length());
 #endif
-    const unsigned clamped_offset = std::min(dom_offset, ToText(node).length());
+    const unsigned clamped_offset = std::min(dom_offset, text_node->length());
     return Position(&node, clamped_offset);
   }
   // For non-text-anchored position, the offset must be either 0 or 1.
@@ -46,13 +46,13 @@
 
 std::pair<const Node&, unsigned> ToNodeOffsetPair(const Position& position) {
   DCHECK(NGOffsetMapping::AcceptsPosition(position)) << position;
-  if (position.AnchorNode()->IsTextNode()) {
+  if (auto* text_node = DynamicTo<Text>(position.AnchorNode())) {
     if (position.IsOffsetInAnchor())
       return {*position.AnchorNode(), position.OffsetInContainerNode()};
     if (position.IsBeforeAnchor())
       return {*position.AnchorNode(), 0};
     DCHECK(position.IsAfterAnchor());
-    return {*position.AnchorNode(), ToText(position.AnchorNode())->length()};
+    return {*position.AnchorNode(), text_node->length()};
   }
   if (position.IsBeforeAnchor())
     return {*position.AnchorNode(), 0};
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc
index a7b13fb..138a43f 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.cc
@@ -23,21 +23,6 @@
          LayoutNGMixin<LayoutBlockFlow>::IsOfType(type);
 }
 
-bool LayoutNGListMarker::IsListMarkerWrapperForBlockContent(
-    const LayoutObject& object) {
-  const auto* block_flow = DynamicTo<LayoutBlockFlow>(object);
-  if (!object.IsAnonymous() || !block_flow)
-    return false;
-  if (const LayoutObject* child = block_flow->FirstChild()) {
-    return child->IsLayoutNGListMarker() &&
-           // The anonymous box should not have other children.
-           // e.g., <li>text<div>block</div></li>
-           // In this case, inline layout can handle the list marker.
-           !child->NextSibling();
-  }
-  return false;
-}
-
 // The LayoutNGListItem this marker belongs to.
 LayoutNGListItem* LayoutNGListMarker::ListItem() const {
   for (LayoutObject* parent = Parent(); parent; parent = parent->Parent()) {
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
index 469de1b..34679807 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
@@ -21,14 +21,6 @@
   explicit LayoutNGListMarker(Element*);
   static LayoutNGListMarker* CreateAnonymous(Document*);
 
-  // True if the LayoutObject is a list marker wrapper for block content.
-  //
-  // Because a list marker in LayoutNG is an inline block, and because CSS
-  // defines all children of a box must be either inline level or block level,
-  // when the content of an list item is block level, the list marker is wrapped
-  // in an anonymous block box. This function determines such an anonymous box.
-  static bool IsListMarkerWrapperForBlockContent(const LayoutObject&);
-
   void WillCollectInlines() override;
 
   bool IsContentImage() const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
index 78c7aa7f..0fe0ce29 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
@@ -222,21 +222,28 @@
   return false;
 }
 
-// Return true if the new constraint space will produce a different sized
-// fragment. This will also return true if any %-block-size children will
-// change size.
-bool SizeWillChange(const NGBlockNode& node,
-                    const NGFragmentGeometry& fragment_geometry,
-                    const NGLayoutResult& layout_result,
-                    const NGConstraintSpace& new_space,
-                    const NGConstraintSpace& old_space) {
+// Given the pre-computed |fragment_geometry| calcuates the
+// |NGLayoutCacheStatus| based on this sizing information. Returns:
+//  - |NGLayoutCacheStatus::kNeedsLayout| if the |new_space| will produce a
+//    different sized fragment, or if any %-block-size children will change
+//    size.
+//  - |NGLayoutCacheStatus::kNeedsSimplifiedLayout| if the block-size of the
+//    fragment will change, *without* affecting any descendants (no descendants
+//    have %-block-sizes).
+//  - |NGLayoutCacheStatus::kHit| otherwise.
+NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatusWithGeometry(
+    const NGBlockNode& node,
+    const NGFragmentGeometry& fragment_geometry,
+    const NGLayoutResult& layout_result,
+    const NGConstraintSpace& new_space,
+    const NGConstraintSpace& old_space) {
   const ComputedStyle& style = node.Style();
   NGBoxFragment fragment(
       style.GetWritingMode(), style.Direction(),
       To<NGPhysicalBoxFragment>(*layout_result.PhysicalFragment()));
 
   if (fragment_geometry.border_box_size.inline_size != fragment.InlineSize())
-    return true;
+    return NGLayoutCacheStatus::kNeedsLayout;
 
   LayoutUnit block_size = fragment_geometry.border_box_size.block_size;
   bool is_initial_block_size_indefinite = block_size == kIndefiniteSize;
@@ -251,7 +258,7 @@
         layout_result.DependsOnPercentageBlockSize()) {
       if (new_space.PercentageResolutionBlockSize() !=
           old_space.PercentageResolutionBlockSize())
-        return true;
+        return NGLayoutCacheStatus::kNeedsLayout;
     }
 
     block_size = ComputeBlockSizeForFragment(
@@ -259,8 +266,7 @@
         layout_result.IntrinsicBlockSize());
   }
 
-  if (block_size != fragment.BlockSize())
-    return true;
+  bool is_block_size_equal = block_size == fragment.BlockSize();
 
   if (layout_result.HasDescendantThatDependsOnPercentageBlockSize()) {
     // %-block-size children of flex-items sometimes don't resolve their
@@ -278,42 +284,54 @@
 
     if (is_old_initial_block_size_indefinite !=
         is_new_initial_block_size_indefinite)
-      return true;
+      return NGLayoutCacheStatus::kNeedsLayout;
 
     // %-block-size children of table-cells have different behaviour if they
     // are in the "measure" or "layout" phase.
     // Instead of trying to capture that logic here, we always miss the cache.
     if (node.IsTableCell() &&
         new_space.IsFixedSizeBlock() != old_space.IsFixedSizeBlock())
-      return true;
+      return NGLayoutCacheStatus::kNeedsLayout;
 
-    // At this point we must have the same block-size for our fragment, so this
-    // is really only checking if any %-block-size children will change size.
-    // This is checks for the quirks-mode %-block-size behaviour.
+    // If our initial block-size is definite, we know that if we change our
+    // block-size we'll affect any descendant that depends on the resulting
+    // percentage block-size.
+    if (!is_block_size_equal && !is_new_initial_block_size_indefinite)
+      return NGLayoutCacheStatus::kNeedsLayout;
+
+    DCHECK(is_block_size_equal || is_new_initial_block_size_indefinite);
+
+    // At this point we know that either we have the same block-size for our
+    // fragment, or our initial block-size was indefinite.
     //
     // The |NGLayoutResult::DependsOnPercentageBlockSize| flag will returns true
     // if we are in quirks mode, and have a descendant that depends on a
     // percentage block-size, however it will also return true if the node
     // itself depends on the %-block-size.
     //
-    // We remove this false-positive by checking if we have an initial
-    // indefinite block-size.
+    // As we only care about the quirks-mode %-block-size behaviour we remove
+    // this false-positive by checking if we have an initial indefinite
+    // block-size.
     if (is_new_initial_block_size_indefinite &&
         layout_result.DependsOnPercentageBlockSize()) {
       DCHECK(is_old_initial_block_size_indefinite);
       if (new_space.PercentageResolutionBlockSize() !=
           old_space.PercentageResolutionBlockSize())
-        return true;
+        return NGLayoutCacheStatus::kNeedsLayout;
       if (new_space.ReplacedPercentageResolutionBlockSize() !=
           old_space.ReplacedPercentageResolutionBlockSize())
-        return true;
+        return NGLayoutCacheStatus::kNeedsLayout;
     }
   }
 
   if (style.MayHavePadding() && fragment_geometry.padding != fragment.Padding())
-    return true;
+    return NGLayoutCacheStatus::kNeedsLayout;
 
-  return false;
+  // If we've reached here we know that we can potentially "stretch"/"shrink"
+  // ourselves without affecting any of our children.
+  // In that case we may be able to perform "simplified" layout.
+  return is_block_size_equal ? NGLayoutCacheStatus::kHit
+                             : NGLayoutCacheStatus::kNeedsSimplifiedLayout;
 }
 
 bool IntrinsicSizeWillChange(
@@ -341,17 +359,19 @@
 
 }  // namespace
 
-bool MaySkipLayout(const NGBlockNode& node,
-                   const NGLayoutResult& cached_layout_result,
-                   const NGConstraintSpace& new_space,
-                   base::Optional<NGFragmentGeometry>* fragment_geometry) {
+NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatus(
+    const NGBlockNode& node,
+    const NGLayoutResult& cached_layout_result,
+    const NGConstraintSpace& new_space,
+    base::Optional<NGFragmentGeometry>* fragment_geometry) {
   DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess);
   DCHECK(cached_layout_result.HasValidConstraintSpaceForCaching());
 
   const NGConstraintSpace& old_space =
       cached_layout_result.GetConstraintSpaceForCaching();
+
   if (!new_space.MaySkipLayout(old_space))
-    return false;
+    return NGLayoutCacheStatus::kNeedsLayout;
 
   if (new_space.AreSizeConstraintsEqual(old_space)) {
     // It is possible that our intrinsic size has changed, check for that here.
@@ -359,25 +379,22 @@
     // |MaySkipLegacyLayout|.
     if (IntrinsicSizeWillChange(node, cached_layout_result, new_space,
                                 fragment_geometry))
-      return false;
+      return NGLayoutCacheStatus::kNeedsLayout;
 
     // We don't have to check our style if we know the constraint space sizes
     // will remain the same.
     if (new_space.AreSizesEqual(old_space))
-      return true;
+      return NGLayoutCacheStatus::kHit;
 
     if (!SizeMayChange(node, new_space, old_space, cached_layout_result))
-      return true;
+      return NGLayoutCacheStatus::kHit;
   }
 
   if (!*fragment_geometry)
     *fragment_geometry = CalculateInitialFragmentGeometry(new_space, node);
 
-  if (SizeWillChange(node, **fragment_geometry, cached_layout_result, new_space,
-                     old_space))
-    return false;
-
-  return true;
+  return CalculateSizeBasedLayoutCacheStatusWithGeometry(
+      node, **fragment_geometry, cached_layout_result, new_space, old_space);
 }
 
 bool MaySkipLegacyLayout(const NGBlockNode& node,
@@ -403,6 +420,82 @@
   return true;
 }
 
+bool MaySkipLayoutWithinBlockFormattingContext(
+    const NGLayoutResult& cached_layout_result,
+    const NGConstraintSpace& new_space,
+    base::Optional<LayoutUnit>* bfc_block_offset) {
+  DCHECK_EQ(cached_layout_result.Status(), NGLayoutResult::kSuccess);
+  DCHECK(cached_layout_result.HasValidConstraintSpaceForCaching());
+  DCHECK(bfc_block_offset);
+
+  const NGConstraintSpace& old_space =
+      cached_layout_result.GetConstraintSpaceForCaching();
+
+  LayoutUnit old_clearance_offset = old_space.ClearanceOffset();
+  LayoutUnit new_clearance_offset = new_space.ClearanceOffset();
+
+  // Determine if we can reuse a result if it was affected by clearance.
+  bool is_pushed_by_floats = cached_layout_result.IsPushedByFloats();
+  if (is_pushed_by_floats) {
+    DCHECK(old_space.HasFloats());
+
+    // We don't attempt to reuse the cached result if the clearance offset
+    // differs from the final BFC-block-offset.
+    //
+    // The |is_pushed_by_floats| flag is also used by nodes who have a *child*
+    // which was pushed by floats. In this case the node may not have a
+    // BFC-block-offset or one equal to the clearance offset.
+    if (!cached_layout_result.BfcBlockOffset() ||
+        *cached_layout_result.BfcBlockOffset() != old_space.ClearanceOffset())
+      return false;
+
+    // We only reuse the cached result if the delta between the
+    // BFC-block-offset, and the clearance offset grows or remains the same. If
+    // it shrinks it may not be affected by clearance anymore as a margin may
+    // push the fragment below the clearance offset instead.
+    //
+    // TODO(layout-dev): If we track if any margins affected this calculation
+    // (with an additional bit on the layout result) we could potentially skip
+    // this check.
+    if (old_clearance_offset - old_space.BfcOffset().block_offset >
+        new_clearance_offset - new_space.BfcOffset().block_offset) {
+      return false;
+    }
+  }
+
+  // Check we have a descendant that *may* be positioned above the block-start
+  // edge. We abort if either the old or new space has floats, as we don't keep
+  // track of how far above the child could be. This case is relatively rare,
+  // and only occurs with negative margins.
+  if (cached_layout_result.MayHaveDescendantAboveBlockStart() &&
+      (old_space.HasFloats() || new_space.HasFloats()))
+    return false;
+
+  // We can now try to adjust the BFC block-offset.
+  if (*bfc_block_offset) {
+    // Check if the previous position may intersect with any floats.
+    if (**bfc_block_offset <
+        old_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
+      return false;
+
+    if (is_pushed_by_floats) {
+      DCHECK_EQ(**bfc_block_offset, old_clearance_offset);
+      *bfc_block_offset = new_clearance_offset;
+    } else {
+      *bfc_block_offset = **bfc_block_offset -
+                          old_space.BfcOffset().block_offset +
+                          new_space.BfcOffset().block_offset;
+    }
+
+    // Check if the new position may intersect with any floats.
+    if (**bfc_block_offset <
+        new_space.ExclusionSpace().ClearanceOffset(EClear::kBoth))
+      return false;
+  }
+
+  return true;
+}
+
 bool IsBlockLayoutComplete(const NGConstraintSpace& space,
                            const NGLayoutResult& result) {
   if (result.Status() != NGLayoutResult::kSuccess)
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h
index 486cddb8..d15b980 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.h
@@ -12,13 +12,31 @@
 class NGConstraintSpace;
 class NGLayoutResult;
 
-// Returns true if for a given |new_space|, the |node| will provide the same
-// |NGLayoutResult| as |cached_layout_result|, and therefore might be able to
-// skip layout.
-bool MaySkipLayout(const NGBlockNode& node,
-                   const NGLayoutResult& cached_layout_result,
-                   const NGConstraintSpace& new_space,
-                   base::Optional<NGFragmentGeometry>* fragment_geometry);
+// NGLayoutCacheStatus indicates what type of cache hit/miss occurred. For
+// various types of misses we may be able to perform less work than a full
+// layout.
+//
+// TODO(ikilpatrick): Link to the simplified layout algorithm definition to
+// explain |kNeedsSimplifiedLayout| when it exists.
+enum class NGLayoutCacheStatus {
+  kHit,                   // Cache hit, no additional work required.
+  kNeedsLayout,           // Cache miss, full layout required.
+  kNeedsSimplifiedLayout  // Cache miss, simplified layout required.
+};
+
+// Calculates the |NGLayoutCacheStatus| based on sizing information. Returns:
+//  - |NGLayoutCacheStatus::kHit| if the size will be the same as
+//    |cached_layout_result|, and therefore might be able to skip layout.
+//  - |NGLayoutCacheStatus::kNeedsSimplifiedLayout| if a simplified layout may
+//    be possible (just based on the sizing information at this point).
+//  - |NGLayoutCacheStatus::kNeedsLayout| if a full layout is required.
+//
+// May pre-compute the |fragment_geometry| while calculating this status.
+NGLayoutCacheStatus CalculateSizeBasedLayoutCacheStatus(
+    const NGBlockNode& node,
+    const NGLayoutResult& cached_layout_result,
+    const NGConstraintSpace& new_space,
+    base::Optional<NGFragmentGeometry>* fragment_geometry);
 
 // Similar to |MaySkipLayout| but for legacy layout roots. Doesn't attempt to
 // pre-compute the geometry of the fragment.
@@ -26,6 +44,17 @@
                          const NGLayoutResult& cached_layout_result,
                          const NGConstraintSpace& new_space);
 
+// Returns true if for a given |new_space|, the |cached_layout_result| won't be
+// affected by clearance, or floats, and therefore might be able to skip
+// layout.
+// Additionally (if this function returns true) it will calculate the new
+// |bfc_block_offset| for the layout result. This may still be |base::nullopt|
+// if not previously set.
+bool MaySkipLayoutWithinBlockFormattingContext(
+    const NGLayoutResult& cached_layout_result,
+    const NGConstraintSpace& new_space,
+    base::Optional<LayoutUnit>* bfc_block_offset);
+
 // Return true if layout is considered complete. In some cases we require more
 // than one layout pass.
 // This function never considers intermediate layouts with
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
index 8b58d98..550fb87e 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
+#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 #include "third_party/blink/renderer/core/layout/geometry/transform_state.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
@@ -822,10 +823,23 @@
 
 namespace {
 
-bool ScrollsWithFrame(const LocalFrame& frame, LayoutObject* object) {
+bool ScrollsWithRootFrame(LayoutObject* object) {
   DCHECK(object);
-  DCHECK(object->EnclosingLayer());
+  DCHECK(object->GetFrame());
 
+  const LocalFrame& frame = *object->GetFrame();
+
+  // If we're in an iframe document, we need to determine if the containing
+  // <iframe> element scrolls with the root frame.
+  if (&frame != &frame.LocalFrameRoot()) {
+    DCHECK(frame.GetDocument());
+    DCHECK(frame.GetDocument()->LocalOwner());
+    DCHECK(frame.GetDocument()->LocalOwner()->GetLayoutObject());
+    return ScrollsWithRootFrame(
+        frame.GetDocument()->LocalOwner()->GetLayoutObject());
+  }
+
+  DCHECK(object->EnclosingLayer());
   if (object->EnclosingLayer()->AncestorScrollingLayer() ==
       frame.ContentLayoutObject()->Layer())
     return true;
@@ -854,6 +868,8 @@
     return;
   }
 
+  LocalFrameView* local_root_view = frame->LocalFrameRoot().View();
+
   if (const LocalFrameView::ScrollableAreaSet* scrollable_areas =
           frame_view->ScrollableAreas()) {
     for (const ScrollableArea* scrollable_area : *scrollable_areas) {
@@ -861,12 +877,12 @@
       if (scrollable_area->UsesCompositedScrolling())
         continue;
 
-      Region* region = ScrollsWithFrame(*frame, scrollable_area->GetLayoutBox())
-                           ? scrolling_region
-                           : fixed_region;
-
-      IntRect box = scrollable_area->ScrollableAreaBoundingBox();
-      region->Unite(box);
+      if (ScrollsWithRootFrame(scrollable_area->GetLayoutBox())) {
+        scrolling_region->Unite(scrollable_area->ScrollableAreaBoundingBox());
+      } else {
+        fixed_region->Unite(local_root_view->DocumentToFrame(
+            scrollable_area->ScrollableAreaBoundingBox()));
+      }
     }
   }
 
@@ -880,20 +896,22 @@
       PaintLayerScrollableArea* scrollable_area =
           box->Layer()->GetScrollableArea();
 
-      Region* region = ScrollsWithFrame(*frame, scrollable_area->GetLayoutBox())
-                           ? scrolling_region
-                           : fixed_region;
+      IntRect bounds_in_frame = box->AbsoluteBoundingBoxRect();
+      IntRect corner_in_frame =
+          scrollable_area->ResizerCornerRect(bounds_in_frame, kResizerForTouch);
 
-      IntRect bounds = box->AbsoluteBoundingBoxRect();
-      // Get the corner in local coords.
-      IntRect corner =
-          scrollable_area->ResizerCornerRect(bounds, kResizerForTouch);
-      // Map corner to top-frame coords.
-      corner = scrollable_area->GetLayoutBox()
-                   ->LocalToAbsoluteQuad(FloatRect(corner),
-                                         kTraverseDocumentBoundaries)
-                   .EnclosingBoundingBox();
-      region->Unite(corner);
+      IntRect corner_in_root_frame =
+          scrollable_area->GetLayoutBox()
+              ->LocalToAbsoluteQuad(FloatRect(corner_in_frame),
+                                    kTraverseDocumentBoundaries)
+              .EnclosingBoundingBox();
+
+      if (ScrollsWithRootFrame(scrollable_area->GetLayoutBox())) {
+        scrolling_region->Unite(
+            local_root_view->FrameToDocument(corner_in_root_frame));
+      } else {
+        fixed_region->Unite(corner_in_root_frame);
+      }
     }
   }
 
@@ -904,13 +922,13 @@
     if (!element->GetLayoutObject())
       continue;
 
-    Region* region = ScrollsWithFrame(*frame, element->GetLayoutObject())
-                         ? scrolling_region
-                         : fixed_region;
-
     if (plugin->WantsWheelEvents()) {
       IntRect box = frame_view->ConvertToRootFrame(plugin->FrameRect());
-      region->Unite(box);
+      if (ScrollsWithRootFrame(element->GetLayoutObject())) {
+        scrolling_region->Unite(local_root_view->FrameToDocument(box));
+      } else {
+        fixed_region->Unite(box);
+      }
     }
   }
 
@@ -918,13 +936,8 @@
   for (Frame* sub_frame = tree.FirstChild(); sub_frame;
        sub_frame = sub_frame->Tree().NextSibling()) {
     if (auto* sub_local_frame = DynamicTo<LocalFrame>(sub_frame)) {
-      // We always use the scrolling_region in subframes because the returned
-      // regions will be relative to the local root frame. Inside an iframe,
-      // there's no way to fix an element to an ancestor frame. i.e. A fixed
-      // region inside the iframe will still translate when the root frame
-      // scrolls; it's only fixed with repsect to its own frame.
       ComputeShouldHandleScrollGestureOnMainThreadRegion(
-          sub_local_frame, scrolling_region, scrolling_region);
+          sub_local_frame, scrolling_region, fixed_region);
     }
   }
 }
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h
index 2239a39a..4efb8fc 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h
@@ -153,6 +153,12 @@
   // outputs a separate region for areas that scroll with the viewport and
   // those that are fixed to it since these regions will need to go on separate
   // layers.
+  // |scrolling_region| is for rects that scroll with the main frame. Since
+  // they they will be stored on the root frame's scrolling contents layer,
+  // they must be specified in the root frame's document coordinates.
+  // |fixed_region| is for rects that are fixed to the main frame. Since they
+  // are stored on the visual viewport's scrolling contents layer, they must be
+  // specified in root frame coordinates.
   void ComputeShouldHandleScrollGestureOnMainThreadRegion(
       const LocalFrame*,
       Region* scrolling_region,
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
index 23d6b26..cc9d811 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
@@ -1362,6 +1362,268 @@
   EXPECT_FALSE(GetFrame()->View()->FrameIsScrollableDidChange());
 }
 
+TEST_P(ScrollingCoordinatorTest, NestedIFramesMainThreadScrollingRegion) {
+  // This page has an absolute IFRAME. It contains a scrollable child DIV
+  // that's nested within an intermediate IFRAME.
+  GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+      false);
+  LoadHTML(R"HTML(
+          <!DOCTYPE html>
+          <style>
+            #spacer {
+              height: 10000px;
+            }
+            iframe {
+              position: absolute;
+              top: 1200px;
+              left: 0px;
+              width: 200px;
+              height: 200px;
+              border: 0;
+            }
+
+          </style>
+          <div id="spacer"></div>
+          <iframe srcdoc="
+              <!DOCTYPE html>
+              <style>
+                body { margin: 0; }
+                iframe { width: 100px; height: 100px; border: 0; }
+              </style>
+              <iframe srcdoc='<!DOCTYPE html>
+                              <style>
+                                body { margin: 0; }
+                                div {
+                                  width: 65px;
+                                  height: 65px;
+                                  overflow: auto;
+                                }
+                                p {
+                                  width: 300px;
+                                  height: 300px;
+                                }
+                              </style>
+                              <div>
+                                <p></p>
+                              </div>'>
+              </iframe>">
+          </iframe>
+      )HTML");
+
+  ForceFullCompositingUpdate();
+
+  // Scroll the frame to ensure the rect is in the correct coordinate space.
+  GetFrame()->GetDocument()->View()->GetScrollableArea()->SetScrollOffset(
+      ScrollOffset(0, 1000), kProgrammaticScroll);
+
+  Region scrolling;
+  Region fixed;
+  Page* page = GetFrame()->GetPage();
+  page->GetScrollingCoordinator()
+      ->ComputeShouldHandleScrollGestureOnMainThreadRegion(
+          To<LocalFrame>(page->MainFrame()), &scrolling, &fixed);
+
+  EXPECT_TRUE(fixed.IsEmpty()) << "Since the DIV will move when the main frame "
+                                  "is scrolled, it should not "
+                                  "be placed in the fixed region.";
+
+  EXPECT_EQ(scrolling.Bounds(), IntRect(0, 1200, 65, 65))
+      << "Since the DIV will move when the main frame is scrolled, it should "
+         "be placed in the scrolling region.";
+}
+
+// Same as above but test that the rect is correctly calculated into the fixed
+// region when the containing iframe is position: fixed.
+TEST_P(ScrollingCoordinatorTest, NestedFixedIFramesMainThreadScrollingRegion) {
+  // This page has a fixed IFRAME. It contains a scrollable child DIV that's
+  // nested within an intermediate IFRAME.
+  GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+      false);
+  LoadHTML(R"HTML(
+          <!DOCTYPE html>
+          <style>
+            #spacer {
+              height: 10000px;
+            }
+            iframe {
+              position: fixed;
+              top: 20px;
+              left: 0px;
+              width: 200px;
+              height: 200px;
+              border: 0;
+            }
+
+          </style>
+          <div id="spacer"></div>
+          <iframe srcdoc="
+              <!DOCTYPE html>
+              <style>
+                body { margin: 0; }
+                iframe { width: 100px; height: 100px; border: 0; }
+              </style>
+              <iframe srcdoc='<!DOCTYPE html>
+                              <style>
+                                body { margin: 0; }
+                                div {
+                                  width: 75px;
+                                  height: 75px;
+                                  overflow: auto;
+                                }
+                                p {
+                                  width: 300px;
+                                  height: 300px;
+                                }
+                              </style>
+                              <div>
+                                <p></p>
+                              </div>'>
+              </iframe>">
+          </iframe>
+      )HTML");
+
+  ForceFullCompositingUpdate();
+
+  // Scroll the frame to ensure the rect is in the correct coordinate space.
+  GetFrame()->GetDocument()->View()->GetScrollableArea()->SetScrollOffset(
+      ScrollOffset(0, 1000), kProgrammaticScroll);
+
+  Region scrolling;
+  Region fixed;
+  Page* page = GetFrame()->GetPage();
+  page->GetScrollingCoordinator()
+      ->ComputeShouldHandleScrollGestureOnMainThreadRegion(
+          To<LocalFrame>(page->MainFrame()), &scrolling, &fixed);
+
+  EXPECT_TRUE(scrolling.IsEmpty()) << "Since the DIV will not move when the "
+                                      "main frame is scrolled, it should "
+                                      "not be placed in the scrolling region.";
+
+  EXPECT_EQ(fixed.Bounds(), IntRect(0, 20, 75, 75))
+      << "Since the DIV not move when the main frame is scrolled, it should be "
+         "placed in the scrolling region.";
+}
+
+TEST_P(ScrollingCoordinatorTest, IframeCompositedScrollingHideAndShow) {
+  GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+      false);
+  LoadHTML(R"HTML(
+          <!DOCTYPE html>
+          <style>
+            body {
+              margin: 0;
+            }
+            iframe {
+              height: 100px;
+              width: 100px;
+            }
+          </style>
+          <iframe id="iframe" srcdoc="
+              <!DOCTYPE html>
+              <style>
+                body {height: 1000px;}
+              </style>"></iframe>
+      )HTML");
+
+  ForceFullCompositingUpdate();
+
+  // Since the main frame isn't scrollable, the NonFastScrollableRegions should
+  // be stored on the visual viewport's scrolling layer, rather than the main
+  // frame's scrolling contents layer.
+  Page* page = GetFrame()->GetPage();
+  cc::Layer* inner_viewport_scroll_layer =
+      page->GetVisualViewport().ScrollLayer()->CcLayer();
+  Element* iframe = GetFrame()->GetDocument()->getElementById("iframe");
+
+  // Should have a NFSR initially.
+  ForceFullCompositingUpdate();
+  EXPECT_FALSE(inner_viewport_scroll_layer->non_fast_scrollable_region()
+                   .bounds()
+                   .IsEmpty());
+
+  // Ensure the frame's scrolling layer didn't get an NFSR.
+  cc::Layer* outer_viewport_scroll_layer =
+      GetFrame()->View()->LayoutViewport()->LayerForScrolling()->CcLayer();
+  EXPECT_TRUE(outer_viewport_scroll_layer->non_fast_scrollable_region()
+                  .bounds()
+                  .IsEmpty());
+
+  // Hiding the iframe should clear the NFSR.
+  iframe->setAttribute(html_names::kStyleAttr, "display: none");
+  ForceFullCompositingUpdate();
+  EXPECT_TRUE(inner_viewport_scroll_layer->non_fast_scrollable_region()
+                  .bounds()
+                  .IsEmpty());
+
+  // Showing it again should compute the NFSR.
+  iframe->setAttribute(html_names::kStyleAttr, "");
+  ForceFullCompositingUpdate();
+  EXPECT_FALSE(inner_viewport_scroll_layer->non_fast_scrollable_region()
+                   .bounds()
+                   .IsEmpty());
+}
+
+// Same as above but the main frame is scrollable. This should cause the non
+// fast scrollable regions to go on the outer viewport's scroll layer.
+TEST_P(ScrollingCoordinatorTest,
+       IframeCompositedScrollingHideAndShowScrollable) {
+  GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
+      false);
+  LoadHTML(R"HTML(
+          <!DOCTYPE html>
+          <style>
+            body {
+              height: 1000px;
+              margin: 0;
+            }
+            iframe {
+              height: 100px;
+              width: 100px;
+            }
+          </style>
+          <iframe id="iframe" srcdoc="
+              <!DOCTYPE html>
+              <style>
+                body {height: 1000px;}
+              </style>"></iframe>
+      )HTML");
+
+  ForceFullCompositingUpdate();
+
+  Page* page = GetFrame()->GetPage();
+  cc::Layer* inner_viewport_scroll_layer =
+      page->GetVisualViewport().ScrollLayer()->CcLayer();
+  Element* iframe = GetFrame()->GetDocument()->getElementById("iframe");
+
+  cc::Layer* outer_viewport_scroll_layer =
+      GetFrame()->View()->LayoutViewport()->LayerForScrolling()->CcLayer();
+
+  // Should have a NFSR initially.
+  ForceFullCompositingUpdate();
+  EXPECT_FALSE(outer_viewport_scroll_layer->non_fast_scrollable_region()
+                   .bounds()
+                   .IsEmpty());
+
+  // Ensure the visual viewport's scrolling layer didn't get an NFSR.
+  EXPECT_TRUE(inner_viewport_scroll_layer->non_fast_scrollable_region()
+                  .bounds()
+                  .IsEmpty());
+
+  // Hiding the iframe should clear the NFSR.
+  iframe->setAttribute(html_names::kStyleAttr, "display: none");
+  ForceFullCompositingUpdate();
+  EXPECT_TRUE(outer_viewport_scroll_layer->non_fast_scrollable_region()
+                  .bounds()
+                  .IsEmpty());
+
+  // Showing it again should compute the NFSR.
+  iframe->setAttribute(html_names::kStyleAttr, "");
+  ForceFullCompositingUpdate();
+  EXPECT_FALSE(outer_viewport_scroll_layer->non_fast_scrollable_region()
+                   .bounds()
+                   .IsEmpty());
+}
+
 TEST_P(ScrollingCoordinatorTest, ScrollOffsetClobberedBeforeCompositingUpdate) {
   // This test fails without BGPT enabled. https://crbug.com/930636.
   if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
diff --git a/third_party/blink/renderer/core/page/touch_adjustment.cc b/third_party/blink/renderer/core/page/touch_adjustment.cc
index 46cf35b2..546ec006 100644
--- a/third_party/blink/renderer/core/page/touch_adjustment.cc
+++ b/third_party/blink/renderer/core/page/touch_adjustment.cc
@@ -183,10 +183,10 @@
   // subtargets for selected or auto-selectable parts of text nodes.
   DCHECK(node->GetLayoutObject());
 
-  if (!node->IsTextNode())
+  auto* text_node = DynamicTo<Text>(node);
+  if (!text_node)
     return AppendBasicSubtargetsForNode(node, subtargets);
 
-  Text* text_node = ToText(node);
   LayoutText* text_layout_object = text_node->GetLayoutObject();
 
   if (text_layout_object->GetFrame()
diff --git a/third_party/blink/renderer/core/paint/inline_text_box_painter.cc b/third_party/blink/renderer/core/paint/inline_text_box_painter.cc
index 480e6b0..7441913 100644
--- a/third_party/blink/renderer/core/paint/inline_text_box_painter.cc
+++ b/third_party/blink/renderer/core/paint/inline_text_box_painter.cc
@@ -539,12 +539,13 @@
 
 DocumentMarkerVector InlineTextBoxPainter::ComputeMarkersToPaint() const {
   Node* const node = inline_text_box_.GetLineLayoutItem().GetNode();
-  if (!node || !node->IsTextNode())
+  auto* text_node = DynamicTo<Text>(node);
+  if (!text_node)
     return DocumentMarkerVector();
 
   DocumentMarkerController& document_marker_controller =
       inline_text_box_.GetLineLayoutItem().GetDocument().Markers();
-  return document_marker_controller.ComputeMarkersToPaint(ToText(*node));
+  return document_marker_controller.ComputeMarkersToPaint(*text_node);
 }
 
 void InlineTextBoxPainter::PaintDocumentMarkers(
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_test.cc b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_test.cc
index 078ee51..513ec15 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_test.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_test.cc
@@ -585,7 +585,7 @@
       "<div id=container>line 1<br><b id=target>line 2</b><br>line "
       "3<br></div>");
   Element& target = *GetDocument().getElementById("target");
-  ToText(*target.firstChild()).setData("abc");
+  To<Text>(*target.firstChild()).setData("abc");
   const NGPaintFragment& container = *GetPaintFragmentByElementId("container");
   auto lines = ToList(container.Children());
   // TODO(kojii): Currently we don't optimzie for <br>. We can do this, then
diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
index 44ab7bfe..230ade2 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
@@ -55,7 +55,8 @@
     const NGPaintFragment& paint_fragment) {
   // TODO(yoichio): Handle first-letter
   Node* const node = paint_fragment.GetNode();
-  if (!node || !node->IsTextNode())
+  auto* text_node = DynamicTo<Text>(node);
+  if (!text_node)
     return DocumentMarkerVector();
   // We don't paint any marker on ellipsis.
   if (paint_fragment.PhysicalFragment().StyleVariant() ==
@@ -64,7 +65,7 @@
 
   DocumentMarkerController& document_marker_controller =
       node->GetDocument().Markers();
-  return document_marker_controller.ComputeMarkersToPaint(ToText(*node));
+  return document_marker_controller.ComputeMarkersToPaint(*text_node);
 }
 
 unsigned GetTextContentOffset(const Text& text, unsigned offset) {
@@ -136,7 +137,7 @@
   const auto& text_fragment =
       To<NGPhysicalTextFragment>(paint_fragment.PhysicalFragment());
   DCHECK(text_fragment.GetNode());
-  const Text& text = ToTextOrDie(*text_fragment.GetNode());
+  const auto& text = To<Text>(*text_fragment.GetNode());
   for (const DocumentMarker* marker : markers_to_paint) {
     const unsigned marker_start_offset =
         GetTextContentOffset(text, marker->StartOffset());
diff --git a/third_party/blink/renderer/core/scroll/scroll_types.h b/third_party/blink/renderer/core/scroll/scroll_types.h
index 46c838904..0933c18 100644
--- a/third_party/blink/renderer/core/scroll/scroll_types.h
+++ b/third_party/blink/renderer/core/scroll/scroll_types.h
@@ -245,21 +245,6 @@
                                                      : ScrollOffset(0, delta);
 }
 
-inline ScrollGranularity ToPlatformScrollGranularity(
-    WebGestureEvent::ScrollUnits units) {
-  switch (units) {
-    case WebGestureEvent::ScrollUnits::kPrecisePixels:
-      return ScrollGranularity::kScrollByPrecisePixel;
-    case WebGestureEvent::ScrollUnits::kPixels:
-      return ScrollGranularity::kScrollByPixel;
-    case WebGestureEvent::ScrollUnits::kPage:
-      return ScrollGranularity::kScrollByPage;
-    default:
-      NOTREACHED();
-      return ScrollGranularity::kScrollByPrecisePixel;
-  }
-}
-
 typedef unsigned ScrollbarControlPartMask;
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index fcd745d..1f92e86d 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -273,7 +273,7 @@
   virtual bool ScrollbarsCanBeActive() const = 0;
 
   // Returns the bounding box of this scrollable area, in the coordinate system
-  // of the top-level FrameView.
+  // of the top-level FrameView's Document.
   virtual IntRect ScrollableAreaBoundingBox() const = 0;
 
   virtual CompositorElementId GetCompositorElementId() const = 0;
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index 71e7067..51cc2c5 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -974,7 +974,7 @@
 
   return node->GetDocument()
       .Markers()
-      .MarkersFor(ToText(*node), marker_types.value())
+      .MarkersFor(To<Text>(*node), marker_types.value())
       .size();
 }
 
@@ -983,7 +983,7 @@
 
   // Only TextMatch markers can be active.
   DocumentMarkerVector markers = node->GetDocument().Markers().MarkersFor(
-      ToText(*node), DocumentMarker::MarkerTypes::TextMatch());
+      To<Text>(*node), DocumentMarker::MarkerTypes::TextMatch());
 
   unsigned active_marker_count = 0;
   for (const auto& marker : markers) {
@@ -1009,7 +1009,7 @@
   }
 
   DocumentMarkerVector markers = node->GetDocument().Markers().MarkersFor(
-      ToText(*node), marker_types.value());
+      To<Text>(*node), marker_types.value());
   if (markers.size() <= index)
     return nullptr;
   return markers[index];
@@ -1230,7 +1230,7 @@
                                           bool active) {
   DCHECK(node);
   node->GetDocument().Markers().SetTextMatchMarkersActive(
-      ToText(*node), start_offset, end_offset, active);
+      To<Text>(*node), start_offset, end_offset, active);
 }
 
 void Internals::setMarkedTextMatchesAreHighlighted(Document* document,
diff --git a/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js b/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
index d827e1f..34feb2f 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
+++ b/third_party/blink/renderer/devtools/front_end/resources/BackgroundServiceView.js
@@ -46,6 +46,9 @@
     this._securityOriginManager.addEventListener(
         SDK.SecurityOriginManager.Events.MainSecurityOriginChanged, () => this._onOriginChanged());
 
+
+    /** @const {!UI.Action} */
+    this._recordAction = /** @type {!UI.Action} */ (UI.actionRegistry.action('background-service.toggle-recording'));
     /** @type {?UI.ToolbarToggle} */
     this._recordButton = null;
 
@@ -89,8 +92,7 @@
    * Creates the toolbar UI element.
    */
   async _setupToolbar() {
-    const action = /** @type {!UI.Action} */ (UI.actionRegistry.action('background-service.toggle-recording'));
-    this._recordButton = UI.Toolbar.createActionButton(action);
+    this._recordButton = UI.Toolbar.createActionButton(this._recordAction);
     this._toolbar.appendToolbarItem(this._recordButton);
 
     const clearButton = new UI.ToolbarButton(ls`Clear`, 'largeicon-clear');
@@ -157,7 +159,7 @@
     if (state.isRecording === this._recordButton.toggled())
       return;
 
-    this._recordButton.setToggled(state.isRecording);
+    this._recordAction.setToggled(state.isRecording);
     this._showPreview(this._selectedEventNode);
   }
 
@@ -201,7 +203,7 @@
       {id: 'timestamp', title: ls`Timestamp`, weight: 8},
       {id: 'eventName', title: ls`Event`, weight: 10},
       {id: 'origin', title: ls`Origin`, weight: 10},
-      {id: 'swSource', title: ls`SW Source`, weight: 4},
+      {id: 'swScope', title: ls`SW Scope`, weight: 2},
       {id: 'instanceId', title: ls`Instance ID`, weight: 10},
     ]);
     const dataGrid = new DataGrid.DataGrid(columns);
@@ -220,22 +222,18 @@
    * @return {!Resources.BackgroundServiceView.EventData}
    */
   _createEventData(serviceEvent) {
-    let swSource = '';
+    let swScope = '';
 
-    // Try to get the script name of the Service Worker registration to be more user-friendly.
-    const registrations = this._serviceWorkerManager.registrations().get(serviceEvent.serviceWorkerRegistrationId);
-    if (registrations && registrations.versions.size) {
-      // Any version will do since we care about the script URL.
-      const version = registrations.versions.values().next().value;
-      // Get the relative path.
-      swSource = version.scriptURL.substr(version.securityOrigin.length);
-    }
+    // Try to get the scope of the Service Worker registration to be more user-friendly.
+    const registration = this._serviceWorkerManager.registrations().get(serviceEvent.serviceWorkerRegistrationId);
+    if (registration)
+      swScope = registration.scopeURL.substr(registration.securityOrigin.length);
 
     return {
       id: this._dataGrid.rootNode().children.length,
       timestamp: UI.formatTimestamp(serviceEvent.timestamp * 1000, /* full= */ true),
       origin: serviceEvent.origin,
-      swSource,
+      swScope,
       eventName: serviceEvent.eventName,
       instanceId: serviceEvent.instanceId,
     };
@@ -285,8 +283,7 @@
       this._preview.contentElement.classList.add('empty-view-scroller');
       const centered = this._preview.contentElement.createChild('div', 'empty-view');
 
-      const action = /** @type {!UI.Action} */ (UI.actionRegistry.action('background-service.toggle-recording'));
-      const landingRecordButton = UI.Toolbar.createActionButton(action);
+      const landingRecordButton = UI.Toolbar.createActionButton(this._recordAction);
 
       const recordKey = createElementWithClass('b', 'background-service-shortcut');
       recordKey.textContent =
@@ -322,7 +319,7 @@
  *    id: number,
  *    timestamp: string,
  *    origin: string,
- *    swSource: string,
+ *    swScope: string,
  *    eventName: string,
  *    instanceId: string,
  * }}
diff --git a/third_party/blink/renderer/devtools/front_end/resources/module.json b/third_party/blink/renderer/devtools/front_end/resources/module.json
index 1fa3e93..6b962ca 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/module.json
+++ b/third_party/blink/renderer/devtools/front_end/resources/module.json
@@ -34,6 +34,17 @@
               "Resources.BackgroundServiceView"
           ],
           "className": "Resources.BackgroundServiceView.ActionDelegate",
+          "category": "Background Services",
+          "options": [
+              {
+                  "value": true,
+                  "title": "Start recording events"
+              },
+              {
+                  "value": false,
+                  "title": "Stop recording events"
+              }
+          ],
           "bindings": [
               {
                   "platform": "windows,linux",
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 9764ccef..ce9ade9 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -1374,12 +1374,12 @@
   if (!GetNode() || !GetDocument() || !GetDocument()->View())
     return;
 
-  if (!GetNode()->IsTextNode())
+  auto* text_node = DynamicTo<Text>(GetNode());
+  if (!text_node)
     return;
 
   DocumentMarkerController& marker_controller = GetDocument()->Markers();
-  DocumentMarkerVector markers =
-      marker_controller.MarkersFor(ToText(*GetNode()));
+  DocumentMarkerVector markers = marker_controller.MarkersFor(*text_node);
   for (DocumentMarker* marker : markers) {
     if (!MarkerTypeIsUsedForAccessibility(marker->GetType()))
       continue;
@@ -1970,8 +1970,8 @@
         name_sources->back().type = name_from;
       }
 
-      if (node && node->IsTextNode())
-        text_alternative = ToText(node)->wholeText();
+      if (auto* text_node = DynamicTo<Text>(node))
+        text_alternative = text_node->wholeText();
       else if (IsHTMLBRElement(node))
         text_alternative = String("\n");
       else
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index 50c29985..c1290fc 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -277,7 +277,7 @@
   }
 
   const String GetDisplayedTime(MediaControlTimeDisplayElement* display) {
-    return ToText(display->firstChild())->data();
+    return To<Text>(display->firstChild())->data();
   }
 
  private:
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc b/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
index ac1efc2..918f61bc 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_video_source.cc
@@ -397,7 +397,7 @@
   base::Optional<media::VideoCaptureFormat> current_format = GetCurrentFormat();
   double frame_rate = current_format ? current_format->frame_rate : 0.0;
   if (current_format && enable_device_rotation_detection_) {
-    track_adapter_->SetSourceFrameSize(current_format->frame_size);
+    track_adapter_->SetSourceFrameSize(IntSize(current_format->frame_size));
   }
   track_adapter_->StartFrameMonitoring(
       frame_rate, base::Bind(&MediaStreamVideoSource::SetMutedState,
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
index 5284e636..42ba74ec 100644
--- a/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter.cc
@@ -568,7 +568,7 @@
       base::BindOnce(&VideoTrackAdapter::StopFrameMonitoringOnIO, this));
 }
 
-void VideoTrackAdapter::SetSourceFrameSize(const gfx::Size& source_frame_size) {
+void VideoTrackAdapter::SetSourceFrameSize(const IntSize& source_frame_size) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   io_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VideoTrackAdapter::SetSourceFrameSizeOnIO,
@@ -664,7 +664,7 @@
 }
 
 void VideoTrackAdapter::SetSourceFrameSizeOnIO(
-    const gfx::Size& source_frame_size) {
+    const IntSize& source_frame_size) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   source_frame_size_ = source_frame_size;
 }
@@ -717,8 +717,8 @@
   // TODO(guidou): Use actual device information instead of this heuristic to
   // detect frames from rotated devices. https://crbug.com/722748
   if (source_frame_size_ &&
-      frame->natural_size().width() == source_frame_size_->height() &&
-      frame->natural_size().height() == source_frame_size_->width()) {
+      frame->natural_size().width() == source_frame_size_->Height() &&
+      frame->natural_size().height() == source_frame_size_->Width()) {
     is_device_rotated = true;
   }
   if (adapters_.IsEmpty()) {
diff --git a/third_party/blink/renderer/modules/mediastream/video_track_adapter.h b/third_party/blink/renderer/modules/mediastream/video_track_adapter.h
index 76a6710..84afd5c 100644
--- a/third_party/blink/renderer/modules/mediastream/video_track_adapter.h
+++ b/third_party/blink/renderer/modules/mediastream/video_track_adapter.h
@@ -16,6 +16,7 @@
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_types.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
+#include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
@@ -74,7 +75,7 @@
                             const OnMutedCallback& on_muted_callback);
   void StopFrameMonitoring();
 
-  void SetSourceFrameSize(const gfx::Size& source_frame_size);
+  void SetSourceFrameSize(const IntSize& source_frame_size);
 
   // Exported for testing.
   //
@@ -103,7 +104,7 @@
   void StartFrameMonitoringOnIO(const OnMutedCallback& on_muted_state_callback,
                                 double source_frame_rate);
   void StopFrameMonitoringOnIO();
-  void SetSourceFrameSizeOnIO(const gfx::Size& frame_size);
+  void SetSourceFrameSizeOnIO(const IntSize& frame_size);
 
   // Compare |frame_counter_snapshot| with the current |frame_counter_|, and
   // inform of the situation (muted, not muted) via |set_muted_state_callback|.
@@ -144,7 +145,7 @@
   float source_frame_rate_;
 
   // Resolution configured on the video source, accessed on the IO-thread.
-  base::Optional<gfx::Size> source_frame_size_;
+  base::Optional<IntSize> source_frame_size_;
 
   DISALLOW_COPY_AND_ASSIGN(VideoTrackAdapter);
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
index b2a9658..7fd4971b 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
@@ -194,10 +194,15 @@
   return consumer_;
 }
 
+bool RTCIceTransport::IsFromPeerConnection() const {
+  return peer_connection_;
+}
+
 IceTransportProxy* RTCIceTransport::ConnectConsumer(
     RTCQuicTransport* consumer) {
   DCHECK(consumer);
   DCHECK(proxy_);
+  DCHECK(!peer_connection_);
   if (!consumer_) {
     consumer_ = consumer;
   } else {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
index 687578da..c4d027f 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h
@@ -102,6 +102,21 @@
   // a QuicTransportProxy. It may be called repeatedly with the same
   // RTCQuicTransport.
   bool HasConsumer() const;
+  // If |this| was created from an RTCPeerConnection.
+  //
+  // Background: This is because we don't reuse an RTCIceTransport that has been
+  // created from an RTCPeerConnection for an RTCQuicTransport (see
+  // bugs.webrtc.org/10591). The core issue here is that the source of truth for
+  // connecting a consumer to ICE is at the P2PTransportChannel. In the case of
+  // RTCPeerConnection, the P2PTransportChannel is already connected and given
+  // to the RTCIceTransport. In the case of the RTCQuicTransport it uses the
+  // RTCIceTransport as the source of truth for enforcing just one connected
+  // consumer. Possible fixes to this issue could include: -Use the
+  // P2PTransportChannel as the source of truth directly (calling this
+  // synchronously from the main thread)
+  // -Asynchronously connect to the P2PTransport - if the count of connected
+  // transports to the P2PTransportChannel is > 1, then throw an exception.
+  bool IsFromPeerConnection() const;
   IceTransportProxy* ConnectConsumer(RTCQuicTransport* consumer);
   void DisconnectConsumer(RTCQuicTransport* consumer);
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
index c1bef2e..83aa06b 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.cc
@@ -4,7 +4,6 @@
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h"
 
 #include "base/containers/span.h"
-#include "base/metrics/histogram_macros.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -97,19 +96,6 @@
   return kWriteBufferSize;
 }
 
-static ReadIntoResult GetReadIntoResult(uint32_t read_amount, bool read_fin) {
-  if (read_fin) {
-    if (read_amount > 0) {
-      return ReadIntoResult::kSomeDataWithFin;
-    }
-    return ReadIntoResult::kNoDataWithFin;
-  }
-  if (read_amount > 0) {
-    return ReadIntoResult::kSomeDataNoFin;
-  }
-  return ReadIntoResult::kNoDataNoFin;
-}
-
 RTCQuicStreamReadResult* RTCQuicStream::readInto(
     NotShared<DOMUint8Array> data,
     ExceptionState& exception_state) {
@@ -131,30 +117,12 @@
       state_ = RTCQuicStreamState::kClosing;
     }
   }
-  UMA_HISTOGRAM_ENUMERATION("RTCQuicStream.ReadIntoResult",
-                            GetReadIntoResult(read_amount, read_fin_));
-  // Collects metrics for how large the read is. This histogram has a max of
-  // 24MB and 50 buckets.
-  UMA_HISTOGRAM_CUSTOM_COUNTS("RTCQuicStream.ReadIntoAmountBytes", read_amount,
-                              1, 24000000, 50);
   auto* result = RTCQuicStreamReadResult::Create();
   result->setAmount(read_amount);
   result->setFinished(read_fin_);
   return result;
 }
 
-static WriteUsage GetWriteUsage(uint32_t write_amount, bool write_fin) {
-  // It's not possible to write nothing.
-  DCHECK(write_amount > 0 || write_fin);
-  if (write_fin) {
-    if (write_amount > 0) {
-      return WriteUsage::kSomeDataWithFin;
-    }
-    return WriteUsage::kNoDataWithFin;
-  }
-  return WriteUsage::kSomeDataNoFin;
-}
-
 void RTCQuicStream::write(const RTCQuicStreamWriteParameters* data,
                           ExceptionState& exception_state) {
   bool finish = data->finish();
@@ -185,12 +153,6 @@
     memcpy(data_vector.data(), write_data->Data(), write_data->length());
     write_buffered_amount_ += write_data->length();
   }
-  UMA_HISTOGRAM_ENUMERATION("RTCQuicStream.WriteUsage",
-                            GetWriteUsage(data_vector.size(), finish));
-  // Collects metrics for how large the write is. This histogram has a max of
-  // 24MB and 50 buckets.
-  UMA_HISTOGRAM_CUSTOM_COUNTS("RTCQuicStream.WriteAmountBytes",
-                              data_vector.size(), 1, 24000000, 50);
   proxy_->WriteData(std::move(data_vector), finish);
   if (finish) {
     wrote_fin_ = true;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h
index 6a3048e..d288f62 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h
@@ -22,35 +22,6 @@
 
 enum class RTCQuicStreamState { kNew, kOpening, kOpen, kClosing, kClosed };
 
-// This enum is used to track how the readInto() API is used in
-// the origin trial. This tracks what the result type is when reading.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class ReadIntoResult {
-  // Reading data with the FIN bit (finish).
-  kSomeDataWithFin = 0,
-  // Reading data without a FIN bit (finish).
-  kSomeDataNoFin = 1,
-  // Reading just the FIN bit (finished reading).
-  kNoDataWithFin = 2,
-  // Nothing read.
-  kNoDataNoFin = 3,
-  kMaxValue = kNoDataNoFin,
-};
-
-// This enum is used to track how the write() API is used in the origin trial.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class WriteUsage {
-  // Writing data with the FIN bit (finish).
-  kSomeDataWithFin = 0,
-  // Writing data without the FIN bit (finish).
-  kSomeDataNoFin = 1,
-  // Writing only the FIN bit (finished writing).
-  kNoDataWithFin = 2,
-  kMaxValue = kNoDataWithFin,
-};
-
 // The RTCQuicStream does not need to be ActiveScriptWrappable since the
 // RTCQuicTransport that it is associated with holds a strong reference to it
 // as long as it is not closed.
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
index 8ae23b7..86b7d56 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_test.cc
@@ -8,8 +8,6 @@
 // for the main thread / worker thread.
 
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_stream.h"
-#include <memory>
-#include "base/test/metrics/histogram_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/modules/peerconnection/adapters/test/mock_p2p_quic_stream.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_quic_stream_event.h"
@@ -1226,205 +1224,4 @@
   EXPECT_EQ("closed", stream->state());
 }
 
-TEST_F(RTCQuicStreamTest, HistogramWriteUsageOnlyData) {
-  V8TestingScope scope;
-
-  auto p2p_quic_stream = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream =
-      CreateQuicStream(scope, p2p_quic_stream.get());
-
-  base::HistogramTester tester;
-  stream->write(CreateWriteParametersWithDataOfLength(2), ASSERT_NO_EXCEPTION);
-  stream->write(CreateWriteParametersWithDataOfLength(2), ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.WriteUsage",
-                            WriteUsage::kSomeDataNoFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramWriteUsageDataWithFin) {
-  V8TestingScope scope;
-
-  auto p2p_quic_stream_1 = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream_1 =
-      CreateQuicStream(scope, p2p_quic_stream_1.get());
-  auto p2p_quic_stream_2 = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream_2 =
-      CreateQuicStream(scope, p2p_quic_stream_2.get());
-
-  base::HistogramTester tester;
-  stream_1->write(CreateWriteParametersWithDataOfLength(2, /*finish=*/true),
-                  ASSERT_NO_EXCEPTION);
-  stream_2->write(CreateWriteParametersWithDataOfLength(2, /*finish=*/true),
-                  ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.WriteUsage",
-                            WriteUsage::kSomeDataWithFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramWriteUsageDataOnlyFin) {
-  V8TestingScope scope;
-
-  auto p2p_quic_stream_1 = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream_1 =
-      CreateQuicStream(scope, p2p_quic_stream_1.get());
-  auto p2p_quic_stream_2 = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream_2 =
-      CreateQuicStream(scope, p2p_quic_stream_2.get());
-
-  base::HistogramTester tester;
-  stream_1->write(CreateWriteParametersWithoutData(/*finish=*/true),
-                  ASSERT_NO_EXCEPTION);
-  stream_2->write(CreateWriteParametersWithoutData(/*finish=*/true),
-                  ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.WriteUsage",
-                            WriteUsage::kNoDataWithFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramWriteAmountBytes) {
-  V8TestingScope scope;
-
-  auto p2p_quic_stream = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream =
-      CreateQuicStream(scope, p2p_quic_stream.get());
-
-  base::HistogramTester tester;
-  stream->write(CreateWriteParametersWithDataOfLength(4), ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  // Expect that 4 bytes were written once.
-  tester.ExpectBucketCount("RTCQuicStream.WriteAmountBytes", 4, 1);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramReadIntoResultOnlyData) {
-  V8TestingScope scope;
-
-  P2PQuicStream::Delegate* stream_delegate = nullptr;
-  auto p2p_quic_stream = std::make_unique<MockP2PQuicStream>(&stream_delegate);
-  Persistent<RTCQuicStream> stream =
-      CreateQuicStream(scope, p2p_quic_stream.get());
-  RunUntilIdle();
-
-  ASSERT_TRUE(stream_delegate);
-  stream_delegate->OnDataReceived({1, 2, 3}, /*fin=*/false);
-  RunUntilIdle();
-
-  base::HistogramTester tester;
-  stream->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(2)),
-                   ASSERT_NO_EXCEPTION);
-  stream->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(1)),
-                   ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.ReadIntoResult",
-                            ReadIntoResult::kSomeDataNoFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramReadIntoResultDataWithFin) {
-  V8TestingScope scope;
-
-  P2PQuicStream::Delegate* stream_delegate_1 = nullptr;
-  auto p2p_quic_stream_1 =
-      std::make_unique<MockP2PQuicStream>(&stream_delegate_1);
-  Persistent<RTCQuicStream> stream_1 =
-      CreateQuicStream(scope, p2p_quic_stream_1.get());
-  P2PQuicStream::Delegate* stream_delegate_2 = nullptr;
-  auto p2p_quic_stream_2 =
-      std::make_unique<MockP2PQuicStream>(&stream_delegate_2);
-  Persistent<RTCQuicStream> stream_2 =
-      CreateQuicStream(scope, p2p_quic_stream_2.get());
-  RunUntilIdle();
-
-  ASSERT_TRUE(stream_delegate_1);
-  ASSERT_TRUE(stream_delegate_2);
-  stream_delegate_1->OnDataReceived({1, 2, 3}, /*fin=*/true);
-  stream_delegate_2->OnDataReceived({1, 2, 3}, /*fin=*/true);
-  RunUntilIdle();
-
-  base::HistogramTester tester;
-  stream_1->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(4)),
-                     ASSERT_NO_EXCEPTION);
-  stream_2->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(4)),
-                     ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.ReadIntoResult",
-                            ReadIntoResult::kSomeDataWithFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramReadIntoResultOnlyFin) {
-  V8TestingScope scope;
-
-  P2PQuicStream::Delegate* stream_delegate_1 = nullptr;
-  auto p2p_quic_stream_1 =
-      std::make_unique<MockP2PQuicStream>(&stream_delegate_1);
-  Persistent<RTCQuicStream> stream_1 =
-      CreateQuicStream(scope, p2p_quic_stream_1.get());
-  P2PQuicStream::Delegate* stream_delegate_2 = nullptr;
-  auto p2p_quic_stream_2 =
-      std::make_unique<MockP2PQuicStream>(&stream_delegate_2);
-  Persistent<RTCQuicStream> stream_2 =
-      CreateQuicStream(scope, p2p_quic_stream_2.get());
-  RunUntilIdle();
-
-  ASSERT_TRUE(stream_delegate_1);
-  ASSERT_TRUE(stream_delegate_2);
-  stream_delegate_1->OnDataReceived({}, /*fin=*/true);
-  stream_delegate_2->OnDataReceived({}, /*fin=*/true);
-  RunUntilIdle();
-
-  base::HistogramTester tester;
-  stream_1->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(0)),
-                     ASSERT_NO_EXCEPTION);
-  stream_2->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(0)),
-                     ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.ReadIntoResult",
-                            ReadIntoResult::kNoDataWithFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramReadIntoResultNothing) {
-  V8TestingScope scope;
-
-  auto p2p_quic_stream = std::make_unique<MockP2PQuicStream>();
-  Persistent<RTCQuicStream> stream =
-      CreateQuicStream(scope, p2p_quic_stream.get());
-
-  base::HistogramTester tester;
-  stream->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(0)),
-                   ASSERT_NO_EXCEPTION);
-  stream->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(0)),
-                   ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  tester.ExpectUniqueSample("RTCQuicStream.ReadIntoResult",
-                            ReadIntoResult::kNoDataNoFin, 2);
-}
-
-TEST_F(RTCQuicStreamTest, HistogramReadCount) {
-  V8TestingScope scope;
-
-  P2PQuicStream::Delegate* stream_delegate = nullptr;
-  auto p2p_quic_stream = std::make_unique<MockP2PQuicStream>(&stream_delegate);
-  Persistent<RTCQuicStream> stream =
-      CreateQuicStream(scope, p2p_quic_stream.get());
-  RunUntilIdle();
-
-  ASSERT_TRUE(stream_delegate);
-  stream_delegate->OnDataReceived({1, 2, 3}, /*fin=*/false);
-  RunUntilIdle();
-
-  base::HistogramTester tester;
-  stream->readInto(NotShared<DOMUint8Array>(DOMUint8Array::Create(3)),
-                   ASSERT_NO_EXCEPTION);
-  RunUntilIdle();
-
-  // Expect that the 3 bytes recorded has a count of 1.
-  tester.ExpectBucketCount("RTCQuicStream.ReadIntoAmountBytes", 3, 1);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
index d2b436cf..5b944b10 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_quic_transport.cc
@@ -115,6 +115,14 @@
                                       "has a connected RTCQuicTransport.");
     return nullptr;
   }
+  if (transport->IsFromPeerConnection()) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidStateError,
+        "Cannot construct an RTCQuicTransport "
+        "with an RTCIceTransport that came from an "
+        "RTCPeerConnection.");
+    return nullptr;
+  }
   for (const auto& certificate : certificates) {
     if (certificate->expires() < ConvertSecondsToDOMTimeStamp(CurrentTime())) {
       exception_state.ThrowTypeError(
diff --git a/third_party/blink/renderer/modules/sms/sms_receiver.idl b/third_party/blink/renderer/modules/sms/sms_receiver.idl
index 320bc1f..7d1c1e3 100644
--- a/third_party/blink/renderer/modules/sms/sms_receiver.idl
+++ b/third_party/blink/renderer/modules/sms/sms_receiver.idl
@@ -15,5 +15,5 @@
 ] interface SMSReceiver : EventTarget {
   readonly attribute SMS sms;
   attribute EventHandler onchange;
-  [CallWith=ScriptState] Promise<void> start();
-};
\ No newline at end of file
+  [CallWith=ScriptState, MeasureAs=SMSReceiverStart] Promise<void> start();
+};
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 2bbf4f6a..0bc2a989 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1026,6 +1026,7 @@
     "graphics/intercepting_canvas.h",
     "graphics/interpolation_space.cc",
     "graphics/interpolation_space.h",
+    "graphics/lab_color_space.h",
     "graphics/link_highlight.h",
     "graphics/logging_canvas.cc",
     "graphics/logging_canvas.h",
@@ -1743,6 +1744,7 @@
     "graphics/gpu/webgl_image_conversion_test.cc",
     "graphics/gpu/webgpu_swap_buffer_provider_test.cc",
     "graphics/graphics_context_test.cc",
+    "graphics/lab_color_space_test.cc",
     "graphics/paint/cull_rect_test.cc",
     "graphics/paint/display_item_client_test.cc",
     "graphics/paint/display_item_raster_invalidator_test.cc",
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
index 5d0233c2..3dd0ea23 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.cc
@@ -35,6 +35,7 @@
   settings_ = new_settings;
 
   SkHighContrastConfig config;
+  transformer_ = base::nullopt;
   switch (settings_.mode) {
     case DarkMode::kOff:
       default_filter_.reset(nullptr);
@@ -58,6 +59,10 @@
     case DarkMode::kInvertLightness:
       config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
       break;
+    case DarkMode::kInvertLightnessLAB:
+      transformer_ = LabColorSpace::RGBLABTransformer();
+      config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness;
+      break;
   }
 
   config.fGrayscale = settings_.grayscale;
@@ -75,7 +80,10 @@
 Color DarkModeFilter::ApplyIfNeeded(const Color& color) {
   if (!default_filter_)
     return color;
-  return Color(default_filter_->filterColor(color.Rgb()));
+  if (!transformer_)
+    return Color(default_filter_->filterColor(color.Rgb()));
+
+  return InvertColor(color);
 }
 
 // TODO(gilmanmh): Investigate making |image| a const reference. This code
@@ -102,10 +110,25 @@
   if (flags.HasShader()) {
     dark_mode_flags.setColorFilter(default_filter_);
   } else {
-    dark_mode_flags.setColor(default_filter_->filterColor(flags.getColor()));
+    auto invertedColor = ApplyIfNeeded(flags.getColor());
+    dark_mode_flags.setColor(
+        SkColorSetRGB(invertedColor.Red(), invertedColor.Green(), invertedColor.Blue()));
   }
 
   return base::make_optional<cc::PaintFlags>(std::move(dark_mode_flags));
 }
 
+Color DarkModeFilter::InvertColor(const Color& color) const {
+  blink::FloatPoint3D rgb = {color.Red() / 255.0f, color.Green() / 255.0f,
+                             color.Blue() / 255.0f};
+  blink::FloatPoint3D lab = transformer_->sRGBToLab(rgb);
+  float invertedL = std::min(110.0f - lab.X(), 100.0f);
+  lab.SetX(invertedL);
+  rgb = transformer_->LabToSRGB(lab);
+
+  return Color(static_cast<unsigned int>(rgb.X() * 255 + 0.5),
+               static_cast<unsigned int>(rgb.Y() * 255 + 0.5),
+               static_cast<unsigned int>(rgb.Z() * 255 + 0.5), color.Alpha());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
index 3f8a1fae..da1114d 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_filter.h
@@ -6,6 +6,7 @@
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/dark_mode_settings.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
+#include "third_party/blink/renderer/platform/graphics/lab_color_space.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 
 class SkColorFilter;
@@ -30,11 +31,14 @@
   base::Optional<cc::PaintFlags> ApplyToFlagsIfNeeded(
       const cc::PaintFlags& flags);
 
+  Color InvertColor(const Color& color) const;
+
  private:
   DarkModeSettings settings_;
 
   sk_sp<SkColorFilter> default_filter_;
   sk_sp<SkColorFilter> image_filter_;
+  base::Optional<LabColorSpace::RGBLABTransformer> transformer_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_settings.h b/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
index 21d1d9e2..e67c87d 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_settings.h
@@ -14,6 +14,7 @@
   kSimpleInvertForTesting,
   kInvertBrightness,
   kInvertLightness,
+  kInvertLightnessLAB,
 };
 
 enum class DarkModeImagePolicy {
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index e7ede40..fdd1c06 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -79,7 +79,6 @@
   }
 
   operator const PaintFlags&() const { return *flags_; }
-
  private:
   const PaintFlags* flags_;
   base::Optional<PaintFlags> dark_mode_flags_;
diff --git a/third_party/blink/renderer/platform/graphics/lab_color_space.h b/third_party/blink/renderer/platform/graphics/lab_color_space.h
new file mode 100644
index 0000000..c470604
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/lab_color_space.h
@@ -0,0 +1,172 @@
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <initializer_list>
+#include <iterator>
+
+#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
+
+// Class to handle color transformation between RGB and CIE L*a*b* color spaces.
+namespace LabColorSpace {
+
+using blink::FloatPoint3D;
+using blink::TransformationMatrix;
+
+static constexpr FloatPoint3D kIlluminantD50 =
+    FloatPoint3D(0.964212f, 1.0f, 0.825188f);
+static constexpr FloatPoint3D kIlluminantD65 =
+    FloatPoint3D(0.95042855f, 1.0f, 1.0889004f);
+
+// All matrices here are 3x3 matrices.
+// They are stored in blink::TransformationMatrix which is 4x4 matrix in the
+// following form.
+// |a b c 0|
+// |d e f 0|
+// |g h i 0|
+// |0 0 0 1|
+
+inline TransformationMatrix mul3x3Diag(const FloatPoint3D& lhs,
+                                       const TransformationMatrix& rhs) {
+  return TransformationMatrix(
+      lhs.X() * rhs.M11(), lhs.Y() * rhs.M12(), lhs.Z() * rhs.M13(), 0.0f,
+      lhs.X() * rhs.M21(), lhs.Y() * rhs.M22(), lhs.Z() * rhs.M23(), 0.0f,
+      lhs.X() * rhs.M31(), lhs.Y() * rhs.M32(), lhs.Z() * rhs.M33(), 0.0f,
+      0.0f, 0.0f, 0.0f, 1.0f);
+}
+
+template <typename T>
+inline constexpr T clamp(T x, T min, T max) {
+  return x < min ? min : x > max ? max : x;
+}
+
+// See https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform.
+inline TransformationMatrix chromaticAdaptation(
+    const TransformationMatrix& matrix,
+    const FloatPoint3D& srcWhitePoint,
+    const FloatPoint3D& dstWhitePoint) {
+  FloatPoint3D srcLMS = matrix.MapPoint(srcWhitePoint);
+  FloatPoint3D dstLMS = matrix.MapPoint(dstWhitePoint);
+  // LMS is a diagonal matrix stored as a float[3]
+  FloatPoint3D LMS = {dstLMS.X() / srcLMS.X(), dstLMS.Y() / srcLMS.Y(),
+                      dstLMS.Z() / srcLMS.Z()};
+  return matrix.Inverse() * mul3x3Diag(LMS, matrix);
+}
+
+class sRGBColorSpace {
+ public:
+  FloatPoint3D toLinear(const FloatPoint3D& v) const {
+    auto EOTF = [](float u) {
+      return u < 0.04045f
+                 ? clamp(u / 12.92f, .0f, 1.0f)
+                 : clamp(std::pow((u + 0.055f) / 1.055f, 2.4f), .0f, 1.0f);
+    };
+    return {EOTF(v.X()), EOTF(v.Y()), EOTF(v.Z())};
+  }
+
+  FloatPoint3D fromLinear(const FloatPoint3D& v) const {
+    auto OETF = [](float u) {
+      return (u < 0.0031308f
+                  ? clamp(12.92 * u, .0, 1.0)
+                  : clamp(1.055 * std::pow(u, 1.0 / 2.4) - 0.055, .0, 1.0));
+    };
+    return {OETF(v.X()), OETF(v.Y()), OETF(v.Z())};
+  }
+
+  // See https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation.
+  FloatPoint3D toXYZ(const FloatPoint3D& rgb) const {
+    return transform_.MapPoint(toLinear(rgb));
+  }
+
+  // See
+  // https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB).
+  FloatPoint3D fromXYZ(const FloatPoint3D& xyz) const {
+    return fromLinear(inverseTransform_.MapPoint(xyz));
+  }
+
+ private:
+  TransformationMatrix kBradford = TransformationMatrix(
+       0.8951f, -0.7502f,  0.0389f, 0.0f,
+       0.2664f,  1.7135f, -0.0685f, 0.0f,
+      -0.1614f,  0.0367f,  1.0296f, 0.0f,
+       0.0f,     0.0f,     0.0f,    1.0f);
+
+  TransformationMatrix xyzTransform = TransformationMatrix(
+      0.41238642f, 0.21263677f, 0.019330615f, 0.0f,
+      0.3575915f,  0.715183f,   0.11919712f,  0.0f,
+      0.18045056f, 0.07218022f, 0.95037293f,  0.0f,
+      0.0f,        0.0f,        0.0f,         1.0f);
+
+  TransformationMatrix transform_ =
+      chromaticAdaptation(kBradford, kIlluminantD65, kIlluminantD50) *
+      xyzTransform;
+  TransformationMatrix inverseTransform_ = transform_.Inverse();
+};
+
+class LABColorSpace {
+ public:
+  // See
+  // https://en.wikipedia.org/wiki/CIELAB_color_space#Reverse_transformation.
+  FloatPoint3D fromXYZ(const FloatPoint3D& v) const {
+    auto f = [](float x) {
+      return x > kSigma3 ? pow(x, 1.0f / 3.0f)
+                         : x / (3 * kSigma2) + 4.0f / 29.0f;
+    };
+
+    float fx = f(v.X() / kIlluminantD50.X());
+    float fy = f(v.Y() / kIlluminantD50.Y());
+    float fz = f(v.Z() / kIlluminantD50.Z());
+
+    float L = 116.0f * fy - 16.0f;
+    float a = 500.0f * (fx - fy);
+    float b = 200.0f * (fy - fz);
+
+    return {clamp(L, 0.0f, 100.0f), clamp(a, -128.0f, 128.0f),
+            clamp(b, -128.0f, 128.0f)};
+  }
+
+  // See
+  // https://en.wikipedia.org/wiki/CIELAB_color_space#Forward_transformation.
+  FloatPoint3D toXYZ(const FloatPoint3D& lab) const {
+    auto invf = [](float x) {
+      return x > kSigma ? pow(x, 3) : 3 * kSigma2 * (x - 4.0f / 29.0f);
+    };
+
+    FloatPoint3D v = {clamp(lab.X(), 0.0f, 100.0f),
+                      clamp(lab.Y(), -128.0f, 128.0f),
+                      clamp(lab.Z(), -128.0f, 128.0f)};
+
+    return {
+        invf((v.X() + 16.0f) / 116.0f + (v.Y() * 0.002f)) * kIlluminantD50.X(),
+        invf((v.X() + 16.0f) / 116.0f) * kIlluminantD50.Y(),
+        invf((v.X() + 16.0f) / 116.0f - (v.Z() * 0.005f)) * kIlluminantD50.Z()};
+  }
+
+ private:
+  static constexpr float kSigma = 6.0f / 29.0f;
+  static constexpr float kSigma2 = 36.0f / 841.0f;
+  static constexpr float kSigma3 = 216.0f / 24389.0f;
+};
+
+class RGBLABTransformer {
+ public:
+  FloatPoint3D sRGBToLab(const FloatPoint3D& rgb) const {
+    FloatPoint3D xyz = rcs.toXYZ(rgb);
+    return lcs.fromXYZ(xyz);
+  }
+
+  FloatPoint3D LabToSRGB(const FloatPoint3D& lab) const {
+    FloatPoint3D xyz = lcs.toXYZ(lab);
+    return rcs.fromXYZ(xyz);
+  }
+
+ private:
+  sRGBColorSpace rcs = sRGBColorSpace();
+  LABColorSpace lcs = LABColorSpace();
+};
+
+}  // namespace LabColorSpace
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
diff --git a/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc b/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc
new file mode 100644
index 0000000..cacdb7ee
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc
@@ -0,0 +1,70 @@
+#include "third_party/blink/renderer/platform/graphics/lab_color_space.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace LabColorSpace {
+
+using blink::FloatPoint3D;
+
+static constexpr FloatPoint3D rgbReferenceWhite =
+    FloatPoint3D(1.0f, 1.0f, 1.0f);
+static constexpr FloatPoint3D labReferenceWhite =
+    FloatPoint3D(100.0f, 0.0f, 0.0f);
+static constexpr float epsilon = 0.0001;
+
+class LabColorSpaceTest : public testing::Test {
+ public:
+  void AssertColorsEqual(const FloatPoint3D& color1,
+                         const FloatPoint3D& color2) {
+    EXPECT_NEAR(color1.X(), color2.X(), epsilon);
+    EXPECT_NEAR(color1.Y(), color2.Y(), epsilon);
+    EXPECT_NEAR(color1.Z(), color2.Z(), epsilon);
+  }
+};
+
+TEST_F(LabColorSpaceTest, XYZTranslation) {
+  sRGBColorSpace colorSpace = sRGBColorSpace();
+
+  // Check whether white transformation is correct
+  FloatPoint3D xyzWhite = colorSpace.toXYZ(rgbReferenceWhite);
+  AssertColorsEqual(xyzWhite, kIlluminantD50);
+
+  FloatPoint3D rgbWhite = colorSpace.fromXYZ(kIlluminantD50);
+  AssertColorsEqual(rgbWhite, rgbReferenceWhite);
+
+  // Check whether transforming sRGB to XYZ and back gives the same RGB values
+  // for some random colors with different r, g, b components.
+  for (unsigned r = 0; r <= 255; r += 40) {
+    for (unsigned g = 0; r <= 255; r += 50) {
+      for (unsigned b = 0; r <= 255; r += 60) {
+        FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
+        FloatPoint3D xyz = colorSpace.toXYZ(rgb);
+        AssertColorsEqual(rgb, colorSpace.fromXYZ(xyz));
+      }
+    }
+  }
+}
+
+TEST_F(LabColorSpaceTest, LabTranslation) {
+  RGBLABTransformer transformer = RGBLABTransformer();
+
+  // Check whether white transformation is correct
+  FloatPoint3D labWhite = transformer.sRGBToLab(rgbReferenceWhite);
+  AssertColorsEqual(labWhite, labReferenceWhite);
+
+  FloatPoint3D rgbWhite = transformer.LabToSRGB(labReferenceWhite);
+  AssertColorsEqual(rgbWhite, rgbReferenceWhite);
+
+  // Check whether transforming sRGB to Lab and back gives the same RGB values
+  // for some random colors with different r, g, b components.
+  for (unsigned r = 0; r <= 255; r += 40) {
+    for (unsigned g = 0; r <= 255; r += 50) {
+      for (unsigned b = 0; r <= 255; r += 60) {
+        FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
+        FloatPoint3D lab = transformer.sRGBToLab(rgb);
+        AssertColorsEqual(rgb, transformer.LabToSRGB(lab));
+      }
+    }
+  }
+}
+
+}  // namespace LabColorSpace
diff --git a/third_party/blink/renderer/platform/web_gesture_event.cc b/third_party/blink/renderer/platform/web_gesture_event.cc
index b767d45..0aac556 100644
--- a/third_party/blink/renderer/platform/web_gesture_event.cc
+++ b/third_party/blink/renderer/platform/web_gesture_event.cc
@@ -20,7 +20,7 @@
   return data.scroll_update.delta_y / frame_scale_;
 }
 
-WebGestureEvent::ScrollUnits WebGestureEvent::DeltaUnits() const {
+WebScrollGranularity WebGestureEvent::DeltaUnits() const {
   if (type_ == WebInputEvent::kGestureScrollBegin)
     return data.scroll_begin.delta_hint_units;
   if (type_ == WebInputEvent::kGestureScrollUpdate)
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index 29171f7..ff081c8 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -52,7 +52,6 @@
 Bug(none) compositing/geometry/limit-layer-bounds-positioned.html [ Failure ]
 Bug(none) compositing/geometry/limit-layer-bounds-transformed.html [ Failure ]
 Bug(none) compositing/iframes/become-composited-nested-iframes.html [ Failure ]
-Bug(none) compositing/iframes/iframe-composited-scrolling-hide-and-show.html [ Failure ]
 Bug(none) compositing/layer-creation/overlap-animation-clipping.html [ Failure ]
 Bug(none) compositing/layer-creation/overlap-animation-container.html [ Failure ]
 Bug(none) compositing/layer-creation/overlap-animation.html [ Failure ]
@@ -128,12 +127,10 @@
 Bug(none) fast/webgl/pixelated.html [ Failure ]
 Bug(none) images/color-profile-svg-foreign-object.html [ Failure ]
 Bug(none) fast/multicol/insane-column-count-and-padding-nested-crash.html [ Crash ]
-Bug(none) fast/sub-pixel/repaint-subpixel-layer-in-subpixel-composited-layer.html [ Failure ]
 Bug(none) fast/sub-pixel/should-not-repaint-subpixel-composited-layer.html [ Failure ]
 Bug(none) fast/sub-pixel/transformed-iframe-copy-on-scroll.html [ Failure ]
 Bug(none) fragmentation/outline-crossing-columns.html [ Crash ]
 Bug(none) fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html [ Failure ]
-Bug(none) http/tests/misc/slow-loading-mask.html [ Failure ]
 Bug(none) http/tests/subresource_filter/ad-highlight-frame-resized.html [ Failure ]
 Bug(none) paint/pagination/pagination-change-clip-crash.html [ Failure ]
 Bug(none) printing/fixed-positioned-headers-and-footers-absolute-covering-some-pages.html [ Failure ]
@@ -247,13 +244,12 @@
 Bug(none) css3/blending/mix-blend-mode-isolation-remove.html [ Failure ]
 
 # virtual/threaded variants of sub-directories and tests already skipped or marked as failing above.
-Bug(none) virtual/threaded/fast/events/pinch/gesture-pinch-zoom-scroll-bubble.html [ Timeout ]
 Bug(none) virtual/threaded/fast/events/pinch/pinch-zoom-pan-within-zoomed-viewport.html [ Failure ]
 Bug(none) virtual/threaded/fast/events/pinch/scroll-visual-viewport-send-boundary-events.html [ Timeout ]
 Bug(none) virtual/threaded/fast/scroll-behavior/first-scroll-runs-on-compositor.html [ Failure ]
 Bug(none) virtual/threaded/compositing/visibility/layer-visible-content.html [ Failure ]
 Bug(none) virtual/threaded/compositing/visibility/visibility-image-layers-dynamic.html [ Failure ]
-Bug(none) virtual/threaded/synthetic_gestures/smooth-scroll-tiny-delta.html [ Failure Timeout ]
+Bug(none) virtual/threaded/synthetic_gestures/smooth-scroll-tiny-delta.html [ Failure Timeout Pass ]
 
 Bug(none) fast/block/float/float-change-composited-scrolling.html [ Failure ]
 
@@ -359,7 +355,6 @@
 
 Bug(none) compositing/overflow/ancestor-with-clip-path.html [ Failure ]
 Bug(none) compositing/overflow/descendant-with-clip-path.html [ Failure ]
-Bug(none) fast/css/pseudo-element-selector-scrollbar-hover.html [ Failure ]
 Bug(none) paint/invalidation/media-audio-no-spurious-repaints.html [ Failure ]
 Bug(none) paint/invalidation/subpixel-shadow-included-in-invalidation.html [ Failure ]
 Bug(none) paint/invalidation/clip/clip-path-constant-repaint.html [ Failure ]
@@ -370,7 +365,6 @@
 
 Bug(none) compositing/squashing/frame-clip-squashed-scrolled.html [ Failure ]
 Bug(none) paint/invalidation/selection/japanese-rl-selection-repaint.html [ Failure ]
-Bug(none) virtual/threaded/external/wpt/feature-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html [ Failure ]
 Bug(none) compositing/overflow/scrollbar-layer-placement-negative-z-index-child-positioned.html [ Failure ]
 
 Bug(none) paint/float/float-under-inline-self-painting-change.html [ Failure ]
@@ -396,17 +390,6 @@
 crbug.com/909749 fast/events/touch/compositor-touch-hit-rects-visibility-hidden.html [ Failure ]
 crbug.com/909749 fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
 crbug.com/909749 fast/events/touch/touch-rect-crash-on-unpromote-layer.html [ Failure ]
-crbug.com/924680 virtual/threaded/fast/events/pinch/pinch-zoom-into-center.html [ Failure ]
-crbug.com/924680 virtual/threaded/synthetic_gestures/synthetic-pinch-zoom-gesture-touchpad.html [ Failure ]
-
-# These were introduced when we started setting the scrolls_outer_viewport bit on ScrollNodes
-Bug(none) virtual/fractional_scrolling_threaded/fast/scrolling/hover-during-scroll.html [ Failure ]
-Bug(none) virtual/fractional_scrolling_threaded/fast/scrolling/no-hover-during-scroll.html [ Failure ]
-Bug(none) virtual/threaded/external/wpt/feature-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html [ Failure ]
-Bug(none) virtual/threaded/external/wpt/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html [ Failure ]
-Bug(none) virtual/threaded/fast/scrolling/hover-during-scroll.html [ Failure ]
-Bug(none) virtual/threaded/fast/scrolling/no-hover-during-scroll.html [ Failure ]
-Bug(none) virtual/threaded/synthetic_gestures/animated-wheel-tiny-delta.html [ Failure ]
 
 # Backdrop filter
 crbug.com/923429 css3/filters/backdrop-filter-basic-blur.html [ Failure ]
@@ -423,8 +406,6 @@
 crbug.com/940033 virtual/fractional_scrolling_threaded/fast/scrolling/wheel-scrolling-over-custom-scrollbar.html [ Failure ]
 crbug.com/940033 virtual/threaded/fast/scrolling/wheel-scrolling-over-custom-scrollbar.html [ Failure ]
 
-Bug(none) virtual/threaded/fast/scrolling/scroll-then-composite.html [ Failure ]
-Bug(none) virtual/fractional_scrolling_threaded/fast/scrolling/scroll-then-composite.html [ Failure ]
 
 # Missing composited layer for fixed-position
 crbug.com/931491 compositing/layer-creation/fixed-position-change-out-of-view-in-view.html [ Failure ]
diff --git a/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show-expected.txt b/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show-expected.txt
deleted file mode 100644
index d545c05..0000000
--- a/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show-expected.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This test makes sure hiding and showing a scrollable iframe correctly updates the set of non-fast scrollable rects.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
-PASS internals.nonFastScrollableRects(document).length is 1
-PASS internals.nonFastScrollableRects(document).length is 0
-PASS internals.nonFastScrollableRects(document).length is 1
diff --git a/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show.html b/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show.html
deleted file mode 100644
index a6bddfb..0000000
--- a/third_party/blink/web_tests/compositing/iframes/iframe-composited-scrolling-hide-and-show.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<script src="../../resources/js-test.js"></script>
-
-<style>
-iframe {
-  height: 100px;
-  width: 100px;
-}
-</style>
-
-<script>
-  description('This test makes sure hiding and showing a scrollable iframe ' +
-              'correctly updates the set of non-fast scrollable rects.');
-
-  onload = function() {
-    if (window.internals) {
-      var frame = document.getElementById('scrollable-iframe');
-      document.body.offsetTop; // Force layout
-      shouldBe('internals.nonFastScrollableRects(document).length', '1');
-      frame.style.display = 'none';
-      document.body.offsetTop; // Force layout
-      shouldBe('internals.nonFastScrollableRects(document).length', '0');
-      frame.style.display = '';
-      document.body.offsetTop; // Force layout
-      shouldBe('internals.nonFastScrollableRects(document).length', '1');
-    }
-  }
-</script>
-
-<iframe id="scrollable-iframe" src="resources/subframe.html"></iframe>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/no-cross-origin-autofocus.sub.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/no-cross-origin-autofocus.sub.html
new file mode 100644
index 0000000..cc01881
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/no-cross-origin-autofocus.sub.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+  <h1>Autofocus shouldn't work in cross-origin iframe.</h1>
+  <iframe id="child" width="200" height="100"></iframe>
+
+  <script>
+    let parent_loaded = false;
+    let child_loaded = false;
+
+    async_test(function(t) {
+      function pingChildIfBothFramesLoaded() {
+        if (parent_loaded && child_loaded)
+          frames[0].postMessage("report_focus_state", "*");
+      }
+
+      window.addEventListener("load", t.step_func(event => {
+        parent_loaded = true;
+        pingChildIfBothFramesLoaded();
+      }));
+
+      window.addEventListener("message", t.step_func(event => {
+        if (event.data == "child_loaded") {
+          child_loaded = true;
+          pingChildIfBothFramesLoaded();
+        } else if (event.data == "child_is_focused") {
+          assert_unreached("The iframe shouldn't get focus");
+        } else if (event.data == "child_is_not_focused") {
+          t.done();
+        }
+      }));
+      document.getElementById("child").src =
+          "http://{{domains[www]}}:{{ports[http][0]}}/html/semantics/forms/autofocus/resources/child-autofocus.html";
+    }, "Autofocus shouldn't work in cross-origin iframe");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/resources/child-autofocus.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/resources/child-autofocus.html
new file mode 100644
index 0000000..afd5601
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/autofocus/resources/child-autofocus.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<input id="target" value="This should be unfocused!" autofocus></input>
+
+<script>
+  let got_focus = false;
+  document.getElementById("target").addEventListener("focus", () => {
+    got_focus = true;
+  });
+
+  window.addEventListener("load", () => {
+    parent.postMessage("child_loaded", "*");
+  });
+
+  window.addEventListener("message", event => {
+    if (event.data == "report_focus_state") {
+      let msg = got_focus ? "child_is_focused" : "child_is_not_focused";
+      parent.postMessage(msg, "*");
+    }
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch.html
similarity index 87%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch.html
index dcea2837..ae2373c3 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch.html
@@ -7,6 +7,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             .scroller > div {
@@ -89,6 +92,7 @@
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var clickIsReceived = false;
 
                 // Check if touch-action attribute works properly for embedded divs
                 // Scrollable-Parent, Child: `auto`, Grand-Child: `none`
@@ -99,13 +103,25 @@
                         assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
                         assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
                     });
-                    test_touchaction.done();
+                    clickIsReceived = true;
                     updateDescriptionComplete();
                 });
 
                 on_event(target0, 'scroll', function(event) {
                     test_touchaction.step(failOnScroll, "scroll received while touch-action is none");
                 });
+
+                // Inject touch inputs.
+                touchScrollInTarget(scrollTarget, 'down').then(function() {
+                    return touchScrollInTarget(scrollTarget, 'right');
+                }).then(function() {
+                    return touchTapInTarget(btnComplete);
+                }).then(function() {
+                    test_touchaction.step(function () {
+                        assert_true(clickIsReceived, "click should be received before the test finishes");
+                    }, "click should be received before the test finishes");
+                    test_touchaction.done();
+                });
             }
         </script>
         <h1>behaviour: none</h1>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch.html
similarity index 86%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch.html
index 16e42954..81f0ea6 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch.html
@@ -7,6 +7,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             .scroller > div {
@@ -84,6 +87,7 @@
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var clickIsReceived = false;
 
                 // Check if touch-action attribute works properly for embedded divs
                 //
@@ -94,13 +98,25 @@
                         assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
                         assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
                     });
-                    test_touchaction.done();
+                    clickIsReceived = true;
                     updateDescriptionComplete();
                 });
 
                 on_event(target0, 'scroll', function(event) {
                     test_touchaction.step(failOnScroll, "scroll received while touch-action is none");
                 });
+
+                // Inject touch inputs.
+                touchScrollInTarget(scrollTarget, 'down').then(function() {
+                    return touchScrollInTarget(scrollTarget, 'right');
+                }).then(function() {
+                    return touchTapInTarget(btnComplete);
+                }).then(function() {
+                    test_touchaction.step(function () {
+                        assert_true(clickIsReceived, "click should be received before the test finishes");
+                    }, "click should be received before the test finishes");
+                    test_touchaction.done();
+                });
             }
         </script>
         <h1>behaviour: none</h1>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch.html
similarity index 87%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch.html
index c75d067e..775708d4 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-x_touch.html
@@ -7,6 +7,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             .scroller > div {
@@ -88,6 +91,7 @@
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var clickIsReceived = false;
 
                 // Check if touch-action attribute works properly for embedded divs
                 //
@@ -98,9 +102,21 @@
                         assert_not_equals(target0.scrollLeft, 0, "scroll x offset should not be 0 in the end of the test");
                         assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
                     });
-                    test_touchaction.done();
+                    clickIsReceived = true;
                     updateDescriptionComplete();
                 });
+
+                // Inject touch inputs.
+                touchScrollInTarget(scrollTarget, 'down').then(function() {
+                    return touchScrollInTarget(scrollTarget, 'right');
+                }).then(function() {
+                    return touchTapInTarget(btnComplete);
+                }).then(function() {
+                    test_touchaction.step(function () {
+                        assert_true(clickIsReceived, "click should be received before the test finishes");
+                    }, "click should be received before the test finishes");
+                    test_touchaction.done();
+                });
             }
         </script>
         <h1>behaviour: pan-x</h1>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch.html
similarity index 87%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch.html
index d420cc56..592cfd6 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch.html
@@ -7,6 +7,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             .scroller > div {
@@ -89,6 +92,7 @@
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var clickIsReceived = false;
 
                 // Check if touch-action attribute works properly for embedded divs
                 // Scrollable-Parent, Child: `pan-x`, Grand-Child: `pan-y`
@@ -99,13 +103,25 @@
                         assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
                         assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
                     });
-                    test_touchaction.done();
+                    clickIsReceived = true;
                     updateDescriptionComplete();
                 });
 
                 on_event(target0, 'scroll', function(event) {
                     test_touchaction.step(failOnScroll, "scroll received while touch-action is none");
                 });
+
+                // Inject touch inputs.
+                touchScrollInTarget(scrollTarget, 'down').then(function() {
+                    return touchScrollInTarget(scrollTarget, 'right');
+                }).then(function() {
+                    return touchTapInTarget(btnComplete);
+                }).then(function() {
+                    test_touchaction.step(function () {
+                        assert_true(clickIsReceived, "click should be received before the test finishes");
+                    }, "click should be received before the test finishes");
+                    test_touchaction.done();
+                });
             }
         </script>
         <h1>behaviour: none</h1>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch.html
similarity index 86%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch.html
index 5e674a1..45ea143 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_touch-action-inherit_parent-none_touch.html
@@ -7,6 +7,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <script src="pointerevent_support.js"></script>
         <style>
             .scroller {
@@ -84,6 +87,7 @@
             function run() {
                 var target0 = document.getElementById("target0");
                 var btnComplete = document.getElementById("btnComplete");
+                var clickIsReceived = false;
 
                 // Check if touch-action attribute works properly for embedded divs
                 //
@@ -94,13 +98,25 @@
                         assert_equals(target0.scrollLeft, 0, "scroll x offset should be 0 in the end of the test");
                         assert_equals(target0.scrollTop, 0, "scroll y offset should be 0 in the end of the test");
                     });
-                    test_touchaction.done();
+                    clickIsReceived = true;
                     updateDescriptionComplete();
                 });
 
                 on_event(target0, 'scroll', function(event) {
                     test_touchaction.step(failOnScroll, "scroll received while touch-action is none");
                 });
+
+                // Inject touch inputs.
+                touchScrollInTarget(scrollTarget, 'down').then(function() {
+                    return touchScrollInTarget(scrollTarget, 'right');
+                }).then(function() {
+                    return touchTapInTarget(btnComplete);
+                }).then(function() {
+                    test_touchaction.step(function () {
+                        assert_true(clickIsReceived, "click should be received before the test finishes");
+                    }, "click should be received before the test finishes");
+                    test_touchaction.done();
+                });
             }
         </script>
         <h1>behaviour: none</h1>
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-quic/RTCQuicTransport.https.html b/third_party/blink/web_tests/external/wpt/webrtc-quic/RTCQuicTransport.https.html
index 12c2371f..a8a25d30 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc-quic/RTCQuicTransport.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc-quic/RTCQuicTransport.https.html
@@ -4,6 +4,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../webrtc/RTCIceTransport-extension-helper.js"></script>
+<script src="../webrtc/RTCPeerConnection-helper.js"></script>
 <script src="RTCQuicTransport-helper.js"></script>
 <script src="../webrtc/dictionary-helper.js"></script>
 <script>
@@ -48,6 +49,23 @@
 }, 'RTCQuicTransport constructor throws if passed an RTCIceTransport that ' +
     'already has an active RTCQuicTransport.');
 
+promise_test(async t => {
+  const pc1 = new RTCPeerConnection();
+  t.add_cleanup(() => pc1.close());
+  const pc2 = new RTCPeerConnection();
+  t.add_cleanup(() => pc2.close());
+
+  pc1.createDataChannel('test');
+  await doSignalingHandshake(pc1, pc2);
+  exchangeIceCandidates(pc1, pc2);
+  await listenToIceConnected(pc1);
+  const iceTransport = pc1.sctp.transport.iceTransport;
+
+  assert_throws('InvalidStateError',
+      () => makeQuicTransport(t, iceTransport));
+}, 'RTCQuicTransport constructor throws if passed an RTCIceTransport that ' +
+    'came from an RTCPeerConnection.');
+
 test(t => {
   const quicTransport = makeStandaloneQuicTransport(t);
   quicTransport.stop();
diff --git a/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scrolled-document.html b/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scrolled-document.html
index 6a01618c..3e95432 100644
--- a/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scrolled-document.html
+++ b/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scrolled-document.html
@@ -103,6 +103,29 @@
                             "Scroller should be scrolled.");
       }
 
+      // Now mutate the scroller so that we force a recompute of its non-fast
+      // scrollable region.
+      {
+        scroller.style.height = "151px";
+        await waitForCompositorCommit();
+      }
+
+      // Again perform a scroll over the scroller rect. Ensure we targetted the
+      // correct scroller.
+      {
+        const delta = 100;
+        const location = elementCenter(scroller);
+        await smoothScroll(delta,
+                           location.x,
+                           location.y,
+                           GestureSourceType.TOUCH_INPUT,
+                           'up',
+                           SPEED_INSTANT);
+
+        assert_equals(scroller.scrollTop, 0,
+                      "Scroller should be scrolled to the top.");
+      }
+
     }, 'Scrolling over an uncomposited scroller inside a composited position' +
        ': fixed element.');
   }
diff --git a/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scroller-scrolled-document.html b/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scroller-scrolled-document.html
index 2dc6c874..a92c715 100644
--- a/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scroller-scrolled-document.html
+++ b/third_party/blink/web_tests/fast/scrolling/non-composited-scroller-in-position-fixed-scroller-scrolled-document.html
@@ -106,6 +106,29 @@
                             "Scroller should be scrolled.");
       }
 
+      // Now mutate the scroller so that we force a recompute of its non-fast
+      // scrollable region.
+      {
+        scroller.style.height = "151px";
+        await waitForCompositorCommit();
+      }
+
+      // Perform another scroll to ensure the recalculated region is still
+      // correct after the scroll.
+      {
+        const delta = 100;
+        const location = elementCenter(scroller);
+        await smoothScroll(delta,
+                           location.x,
+                           location.y,
+                           GestureSourceType.TOUCH_INPUT,
+                           'up',
+                           SPEED_INSTANT);
+
+        assert_equals(scroller.scrollTop, 0,
+                      "Scroller should be scrolled to the top.");
+      }
+
     }, 'Scrolling over an uncomposited scroller inside a composited position' +
        ': fixed element.');
   }
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/get-request-post-data-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/get-request-post-data-expected.txt
index dbbb20c..5980ee7 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/get-request-post-data-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/get-request-post-data-expected.txt
@@ -30,6 +30,6 @@
 Data length: 64
 Testing request body eviction
 Data included: true, has post data: true
-Data length: 512
+Did not fetch data: No post data available for the request
 Did not fetch data: No post data available for the request
 
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
index 0e3e22f0..01ebc1d 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
index 310a6f2..6fed243 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
index 5b4aadf..42bb074 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-off/text-input-elements-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
index 8257781..b0b264b5 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-mode/paint/dark-mode/native-theme-on/text-input-elements-expected.png
Binary files differ
diff --git a/third_party/webxr_test_pages/webxr-samples/360-photos.html b/third_party/webxr_test_pages/webxr-samples/360-photos.html
index 621b3a9..b5b987f 100644
--- a/third_party/webxr_test_pages/webxr-samples/360-photos.html
+++ b/third_party/webxr_test_pages/webxr-samples/360-photos.html
@@ -115,12 +115,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
index b64d3e9..3019f07 100644
--- a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
@@ -141,12 +141,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html b/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
index 1203704b..2e5dab5 100644
--- a/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
+++ b/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
@@ -117,6 +117,7 @@
       }
 
       function onSessionStarted(session) {
+        session.mode = 'immersive-vr';
         xrButton.setSession(session);
 
         session.addEventListener('end', onSessionEnded);
diff --git a/third_party/webxr_test_pages/webxr-samples/input-selection.html b/third_party/webxr_test_pages/webxr-samples/input-selection.html
index 9bc27ff..b022ad9 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-selection.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-selection.html
@@ -295,6 +295,10 @@
       }
 
       function onSessionStarted(session) {
+        if (!session.mode) {
+          session.mode = 'inline';
+        }
+
         session.addEventListener('end', onSessionEnded);
 
         // By listening for the 'select' event we can find out when the user has
diff --git a/third_party/webxr_test_pages/webxr-samples/input-tracking.html b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
index 04047e14..76e0ed5 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-tracking.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
@@ -119,12 +119,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/js/webxr-polyfill.js b/third_party/webxr_test_pages/webxr-samples/js/webxr-polyfill.js
index 9344e85..875b412 100644
--- a/third_party/webxr_test_pages/webxr-samples/js/webxr-polyfill.js
+++ b/third_party/webxr_test_pages/webxr-samples/js/webxr-polyfill.js
@@ -1196,6 +1196,11 @@
           try {
             sessionId = $await_1;
             session = new XRSession(this[PRIVATE$11].polyfill, this, sessionOptions, sessionId);
+            // TODO(crbug.com/958922): remove polyfill from tests.
+            if (sessionOptions.immersive)
+              session.mode = 'immersive-vr';
+            else
+              session.mode = 'inline';
             if (sessionOptions.immersive) {
               this[PRIVATE$11].immersiveSession = session;
             } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/magic-window.html b/third_party/webxr_test_pages/webxr-samples/magic-window.html
index 7fde66a..017a6dfc 100644
--- a/third_party/webxr_test_pages/webxr-samples/magic-window.html
+++ b/third_party/webxr_test_pages/webxr-samples/magic-window.html
@@ -102,12 +102,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         if (!gl) {
diff --git a/third_party/webxr_test_pages/webxr-samples/mirroring.html b/third_party/webxr_test_pages/webxr-samples/mirroring.html
index 1aa95455..718d97e2 100644
--- a/third_party/webxr_test_pages/webxr-samples/mirroring.html
+++ b/third_party/webxr_test_pages/webxr-samples/mirroring.html
@@ -90,6 +90,7 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           // In order to mirror an exclusive session, we must provide
           // an XRPresentationContext, which indicates the canvas that will
           // contain results of the session's rendering.
diff --git a/third_party/webxr_test_pages/webxr-samples/positional-audio.html b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
index 77188f0..06b533e 100644
--- a/third_party/webxr_test_pages/webxr-samples/positional-audio.html
+++ b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
@@ -331,12 +331,16 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode) {
+          session.mode = 'inline';
+        }
         session.addEventListener('end', onSessionEnded);
 
         session.addEventListener('selectstart', onSelectStart);
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
index a3df8810..51afc5f 100644
--- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
+++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
@@ -114,6 +114,7 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-ar').then((session) => {
+              session.mode = 'immersive-ar';
               xrButton.setSession(session);
               onSessionStarted(session);
         });
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar.html
index ba1c455d1..a746e53a 100644
--- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar.html
+++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar.html
@@ -103,6 +103,7 @@
         // This will likely prompt the user to allow camera use, so the promise
         // may remain outstanding for a while.
         navigator.xr.requestSession('immersive-ar').then((session) => {
+            session.mode = 'immersive-ar';
             xrButton.setSession(session);
             onSessionStarted(session);
         });
diff --git a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
index 9f6e3cc3..8c4b624 100644
--- a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
@@ -118,12 +118,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/room-scale.html b/third_party/webxr_test_pages/webxr-samples/room-scale.html
index 9223484..adacde5d 100644
--- a/third_party/webxr_test_pages/webxr-samples/room-scale.html
+++ b/third_party/webxr_test_pages/webxr-samples/room-scale.html
@@ -104,6 +104,7 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -123,6 +124,8 @@
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
index 375e7660..a6231098 100644
--- a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
+++ b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
@@ -103,6 +103,7 @@
 
           navigator.xr.requestSession('inline')
               .then((session) => {
+                session.mode = 'inline';
                 document.body.appendChild(outputCanvas);
                 session.updateRenderState({
                   outputContext: outputCanvas.getContext('xrpresent')
@@ -141,6 +142,7 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
 
diff --git a/third_party/webxr_test_pages/webxr-samples/stereo-video.html b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
index 7d10be9..1f3a731 100644
--- a/third_party/webxr_test_pages/webxr-samples/stereo-video.html
+++ b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
@@ -166,12 +166,16 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode) {
+          session.mode = 'inline';
+        }
         session.addEventListener('end', onSessionEnded);
         session.addEventListener('select', (ev) => {
           let refSpace = ev.frame.session.mode.startsWith('immersive') ?
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
index 93bc3b1..0c5f3789 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
@@ -306,14 +306,17 @@
       }
 
       function onRequestSession() {
-        let xrOptions = appSettings.arMode ? 'immersive-ar' : 'immersive-vr';
-        navigator.xr.requestSession(xrOptions).then((session) => {
+        let xrMode = appSettings.arMode ? 'immersive-ar' : 'immersive-vr';
+        navigator.xr.requestSession(xrMode).then((session) => {
+          session.mode = xrMode;
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
index 968d7ad..7dca22d 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
@@ -106,12 +106,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
index 0d22f8af..e24fc2c 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
@@ -216,12 +216,16 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode) {
+          session.mode = 'inline';
+        }
         session.addEventListener('end', onSessionEnded);
 
         session.addEventListener('select', (ev) => {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
index 5ea081a..eb326dc 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
@@ -113,12 +113,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         session.addEventListener('selectstart', onSelectStart);
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
index ccbac7c..168773b 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
@@ -126,12 +126,17 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode) {
+          session.mode = 'inline';
+        }
+
         session.addEventListener('end', onSessionEnded);
 
         // By listening for the 'select' event we can find out when the user has
diff --git a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
index 3208f70..dd021ac 100644
--- a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
+++ b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
@@ -142,12 +142,15 @@
 
       function onRequestSession() {
         navigator.xr.requestSession('immersive-vr').then((session) => {
+          session.mode = 'immersive-vr';
           xrButton.setSession(session);
           onSessionStarted(session);
         });
       }
 
       function onSessionStarted(session) {
+        if (!session.mode)
+          session.mode = 'inline';
         session.addEventListener('end', onSessionEnded);
 
         initGL();
diff --git a/third_party/webxr_test_pages/webxr-samples/xr-barebones.html b/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
index a6bb7c5..f9a04bff 100644
--- a/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
+++ b/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
@@ -106,6 +106,7 @@
       // Called when we've successfully acquired a XRSession. In response we
       // will set up the necessary session state and kick off the frame loop.
       function onSessionStarted(session) {
+        session.mode = 'immersive-vr';
         xrSession = session;
         xrButton.innerHTML = 'Exit XR';
 
diff --git a/third_party/webxr_test_pages/webxr-samples/xr-presentation.html b/third_party/webxr_test_pages/webxr-samples/xr-presentation.html
index 7a2303a..5ee75b5 100644
--- a/third_party/webxr_test_pages/webxr-samples/xr-presentation.html
+++ b/third_party/webxr_test_pages/webxr-samples/xr-presentation.html
@@ -100,6 +100,7 @@
       // Called when we've successfully acquired a XRSession. In response we
       // will set up the necessary session state and kick off the frame loop.
       function onSessionStarted(session) {
+        session.mode = 'immersive-vr';
         // This informs the 'Enter XR' button that the session has started and
         // that it should display 'Exit XR' instead.
         xrButton.setSession(session);
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f63bcd6..314c6141d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -22976,6 +22976,7 @@
   <int value="2877" label="VerticalScrollbarThumbScrollingWithTouch"/>
   <int value="2878" label="HorizontalScrollbarThumbScrollingWithMouse"/>
   <int value="2879" label="HorizontalScrollbarThumbScrollingWithTouch"/>
+  <int value="2880" label="SMSReceiverStart"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -32882,6 +32883,7 @@
   <int value="-1319688939" label="ignore-gpu-blacklist"/>
   <int value="-1318914924" label="OverflowIconsForMediaControls:enabled"/>
   <int value="-1314603238" label="ChromeHomePullToRefreshIphAtTop:enabled"/>
+  <int value="-1313810940" label="StrictOriginIsolation:disabled"/>
   <int value="-1311575452"
       label="AutofillEnforceMinRequiredFieldsForUpload:disabled"/>
   <int value="-1311133348" label="VrBrowsingNativeAndroidUi:enabled"/>
@@ -33229,6 +33231,7 @@
   <int value="-797310986" label="CSSFragmentIdentifiers:disabled"/>
   <int value="-795600188" label="disable-async-dns"/>
   <int value="-793921836" label="ShowAllDialogsWithViewsToolkit:disabled"/>
+  <int value="-793383355" label="StrictOriginIsolation:enabled"/>
   <int value="-792079435" label="EnableAppsGridGapFeature:disabled"/>
   <int value="-790036192" label="overscroll-start-threshold"/>
   <int value="-788074946"
@@ -36667,6 +36670,14 @@
   <int value="3" label="Low and High confidence."/>
 </enum>
 
+<enum name="MdnsResponderServiceError">
+  <int value="0" label="Fail to start manager"/>
+  <int value="1" label="Fail to create responder"/>
+  <int value="2" label="Fatal socket handler error"/>
+  <int value="3" label="Invalid IP to register name"/>
+  <int value="4" label="Conflicting name resolution"/>
+</enum>
+
 <enum name="MediaCommand">
   <int value="0" label="Resume"/>
   <int value="1" label="Pause"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 84eafb1..20fdf4d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -3266,6 +3266,18 @@
   </summary>
 </histogram>
 
+<histogram name="Android.WebView.onReceivedHttpError.StatusCode"
+    enum="HttpResponseCode" expires_after="2020-04-11">
+  <owner>timvolodine@chromium.org</owner>
+  <owner>tobiasjs@chromium.org</owner>
+  <owner>ntfschr@chromium.org</owner>
+  <summary>
+    The WebViewClient http resonse status code as returned by the
+    onReceivedHttpError callback This is recorded regardless of whether the
+    network service is enabled or disabled.
+  </summary>
+</histogram>
+
 <histogram name="Android.WebView.onSafeBrowsingHit.ThreatType"
     enum="AndroidWebViewClientSafeBrowsingThreatType"
     expires_after="2020-04-24">
@@ -75321,6 +75333,17 @@
   </summary>
 </histogram>
 
+<histogram name="NetworkService.MdnsResponder.ServiceError"
+    enum="MdnsResponderServiceError" expires_after="M77">
+  <owner>qingsi@chromium.org</owner>
+  <owner>jeroendb@chromium.org</owner>
+  <summary>
+    Errors that the mDNS responder service encounters during its lifetime. These
+    may be system errors in mDNS socket creation and IO, or user-induced by
+    invalid IP addresses.
+  </summary>
+</histogram>
+
 <histogram name="NetworkService.ShutdownTime" units="ms">
   <owner>jam@chromium.org</owner>
   <summary>
@@ -104114,6 +104137,9 @@
 </histogram>
 
 <histogram name="RTCQuicStream.ReadIntoAmountBytes" expires_after="M75">
+  <obsolete>
+    Removed in M76.
+  </obsolete>
   <owner>shampson@chromium.org</owner>
   <owner>steveanton@chromium.org</owner>
   <summary>
@@ -104125,6 +104151,9 @@
 
 <histogram name="RTCQuicStream.ReadIntoResult"
     enum="RTCQuicStreamReadIntoResult" expires_after="M75">
+  <obsolete>
+    Removed in M76.
+  </obsolete>
   <owner>shampson@chromium.org</owner>
   <owner>steveanton@chromium.org</owner>
   <summary>
@@ -104135,6 +104164,9 @@
 </histogram>
 
 <histogram name="RTCQuicStream.WriteAmountBytes" expires_after="M75">
+  <obsolete>
+    Removed in M76.
+  </obsolete>
   <owner>shampson@chromium.org</owner>
   <owner>steveanton@chromium.org</owner>
   <summary>
@@ -104146,6 +104178,9 @@
 
 <histogram name="RTCQuicStream.WriteUsage" enum="RTCQuicStreamWriteUsage"
     expires_after="M75">
+  <obsolete>
+    Removed in M76.
+  </obsolete>
   <owner>shampson@chromium.org</owner>
   <owner>steveanton@chromium.org</owner>
   <summary>
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 9d03eed..5d3478f 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -324,8 +324,7 @@
 crbug.com/953371 [ Win ] v8.browsing_desktop/browse:social:twitter_infinite_scroll:2018 [ Skip ]
 crbug.com/954959 [ Linux ] v8.browsing_desktop/browse:media:pinterest:2018 [ Skip ]
 crbug.com/954959 [ Linux ] v8.browsing_desktop/browse:tools:maps [ Skip ]
-crbug.com/958422 [ Linux ] v8.browsing_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
-crbug.com/958507 [ Mac ] v8.browsing_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
+crbug.com/958422 [ All ] v8.browsing_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
 crbug.com/958507 [ Mac ] v8.browsing_desktop/browse:media:imgur [ Skip ]
 
 # Benchmark v8.browsing_desktop-future
@@ -334,9 +333,8 @@
 crbug.com/773084 [ Mac ] v8.browsing_desktop-future/browse:tools:maps [ Skip ]
 crbug.com/906654 [ All ] v8.browsing_desktop-future/browse:search:google [ Skip ]
 crbug.com/953371 [ Win ] v8.browsing_desktop-future/browse:social:twitter_infinite_scroll:2018 [ Skip ]
-crbug.com/958422 [ Linux ] v8.browsing_desktop-future/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
+crbug.com/958422 [ All ] v8.browsing_desktop-future/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
 crbug.com/958507 [ Mac ] v8.browsing_desktop-future/browse:media:imgur [ Skip ]
-crbug.com/958507 [ Mac ] v8.browsing_desktop-future/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
 
 # Benchmark: v8.browsing_mobile
 crbug.com/958034 [ Android_Go_Webview ] v8.browsing_mobile/* [ Skip ]
diff --git a/ui/accessibility/ax_node_position_unittest.cc b/ui/accessibility/ax_node_position_unittest.cc
index 1f886c67..60daf52 100644
--- a/ui/accessibility/ax_node_position_unittest.cc
+++ b/ui/accessibility/ax_node_position_unittest.cc
@@ -2059,6 +2059,63 @@
   EXPECT_EQ(*text_position1, *text_position2);
 }
 
+TEST_F(AXPositionTest, CreateNextAnchorPosition) {
+  // This test updates the tree structure to test a specific edge case -
+  // CreateNextAnchorPosition on an empty text field.
+  AXNodePosition::SetTreeForTesting(nullptr);
+
+  ui::AXNodeData root_data;
+  root_data.id = 0;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+
+  ui::AXNodeData text_data;
+  text_data.id = 1;
+  text_data.role = ax::mojom::Role::kStaticText;
+  text_data.SetName("some text");
+
+  ui::AXNodeData text_field_data;
+  text_field_data.id = 2;
+  text_field_data.role = ax::mojom::Role::kTextField;
+
+  ui::AXNodeData empty_text_data;
+  empty_text_data.id = 3;
+  empty_text_data.role = ax::mojom::Role::kStaticText;
+  empty_text_data.SetName("");
+
+  ui::AXNodeData more_text_data;
+  more_text_data.id = 4;
+  more_text_data.role = ax::mojom::Role::kStaticText;
+  more_text_data.SetName("more text");
+
+  root_data.child_ids = {1, 2, 4};
+  text_field_data.child_ids = {3};
+
+  ui::AXTreeUpdate update;
+  ui::AXTreeData tree_data;
+  tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  update.tree_data = tree_data;
+  update.has_tree_data = true;
+  update.root_id = root_data.id;
+  update.nodes = {root_data, text_data, text_field_data, empty_text_data,
+                  more_text_data};
+
+  std::unique_ptr<AXTree> new_tree;
+  new_tree.reset(new AXTree(update));
+  AXNodePosition::SetTreeForTesting(new_tree.get());
+
+  // Test that CreateNextAnchorPosition will successfully navigate past the
+  // empty text field.
+  TestPositionType text_position1 = AXNodePosition::CreateTextPosition(
+      new_tree->data().tree_id, text_data.id, 8 /* text_offset */,
+      ax::mojom::TextAffinity::kDownstream);
+  ASSERT_NE(nullptr, text_position1);
+  ASSERT_FALSE(text_position1->CreateNextAnchorPosition()
+                   ->CreateNextAnchorPosition()
+                   ->IsNullPosition());
+
+  AXNodePosition::SetTreeForTesting(&tree_);
+}
+
 //
 // Parameterized tests.
 //
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index f2575bbd..637a808 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -1237,14 +1237,16 @@
 
     if (AnchorChildCount()) {
       if (IsTreePosition()) {
-        return CreateChildPositionAt(child_index_);
+        if (child_index_ < AnchorChildCount())
+          return CreateChildPositionAt(child_index_);
       } else {
         // We have to find the child node that encompasses the current text
         // offset.
         AXPositionInstance tree_position = AsTreePosition();
         DCHECK(tree_position);
-        return tree_position->CreateChildPositionAt(
-            tree_position->child_index_);
+        int child_index = tree_position->child_index_;
+        if (child_index < tree_position->AnchorChildCount())
+          return tree_position->CreateChildPositionAt(child_index);
       }
     }
 
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 3f9e855..def47a6 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -1590,6 +1590,125 @@
       /*expected_count*/ -6);
 }
 
+// Verify that the endpoint can move past an empty text field.
+TEST_F(AXPlatformNodeTextRangeProviderTest,
+       TestITextRangeProviderMoveEndpointByUnitTextField) {
+  ui::AXNodeData root_data;
+  root_data.id = 0;
+  root_data.role = ax::mojom::Role::kRootWebArea;
+
+  ui::AXNodeData group1_data;
+  group1_data.id = 1;
+  group1_data.role = ax::mojom::Role::kGenericContainer;
+
+  ui::AXNodeData text_data;
+  text_data.id = 2;
+  text_data.role = ax::mojom::Role::kStaticText;
+  std::string text_content = "some text";
+  text_data.SetName(text_content);
+  std::vector<int> word_start_offsets, word_end_offsets;
+  ComputeWordBoundariesOffsets(text_content, word_start_offsets,
+                               word_end_offsets);
+  text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+                                word_start_offsets);
+  text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+                                word_end_offsets);
+
+  ui::AXNodeData text_input_data;
+  text_input_data.id = 3;
+  text_input_data.role = ax::mojom::Role::kTextField;
+
+  ui::AXNodeData group2_data;
+  group2_data.id = 4;
+  group2_data.role = ax::mojom::Role::kGenericContainer;
+
+  ui::AXNodeData more_text_data;
+  more_text_data.id = 5;
+  more_text_data.role = ax::mojom::Role::kStaticText;
+  text_content = "more text";
+  more_text_data.SetName(text_content);
+  ComputeWordBoundariesOffsets(text_content, word_start_offsets,
+                               word_end_offsets);
+  more_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+                                     word_start_offsets);
+  more_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+                                     word_end_offsets);
+
+  ui::AXNodeData empty_text_data;
+  empty_text_data.id = 6;
+  empty_text_data.role = ax::mojom::Role::kStaticText;
+  text_content = "";
+  empty_text_data.SetName(text_content);
+  ComputeWordBoundariesOffsets(text_content, word_start_offsets,
+                               word_end_offsets);
+  empty_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+                                      word_start_offsets);
+  empty_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+                                      word_end_offsets);
+
+  root_data.child_ids = {1, 3, 4};
+  group1_data.child_ids = {2};
+  text_input_data.child_ids = {6};
+  group2_data.child_ids = {5};
+
+  ui::AXTreeUpdate update;
+  ui::AXTreeData tree_data;
+  tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  update.tree_data = tree_data;
+  update.has_tree_data = true;
+  update.root_id = root_data.id;
+  update.nodes = {root_data,   group1_data,    text_data,      text_input_data,
+                  group2_data, more_text_data, empty_text_data};
+
+  Init(update);
+
+  // Set up variables from the tree for testing.
+  AXNode* root_node = GetRootNode();
+  AXNodePosition::SetTreeForTesting(tree_.get());
+  AXNode* text_node = root_node->children()[0]->children()[0];
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  GetTextRangeProviderFromTextNode(text_range_provider, text_node);
+
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
+
+  int count;
+  // Tests for TextUnit_Character
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 1, &count));
+  ASSERT_EQ(1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textm");
+
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ -1, &count));
+  ASSERT_EQ(-1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
+
+  // Tests for TextUnit_Word
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ 1, &count));
+  ASSERT_EQ(1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textmore");
+
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Word, /*count*/ -1, &count));
+  ASSERT_EQ(-1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
+
+  // Tests for TextUnit_Line
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ 1, &count));
+  ASSERT_EQ(1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some textmore text");
+
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Line, /*count*/ -1, &count));
+  ASSERT_EQ(-1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"some text");
+
+  AXNodePosition::SetTreeForTesting(nullptr);
+}
+
 TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderCompare) {
   Init(BuildTextDocument({"some text", "some text"}));
   AXNodePosition::SetTreeForTesting(tree_.get());
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index d154a67f5..609da5f 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -55,7 +55,6 @@
     "mus/mus_context_factory.h",
     "mus/mus_mouse_location_updater.h",
     "mus/mus_types.h",
-    "mus/os_exchange_data_provider_mus.h",
     "mus/property_converter.h",
     "mus/property_utils.h",
     "mus/system_input_injector_mus.h",
@@ -129,7 +128,6 @@
     "mus/mus_lsi_allocator.cc",
     "mus/mus_lsi_allocator.h",
     "mus/mus_mouse_location_updater.cc",
-    "mus/os_exchange_data_provider_mus.cc",
     "mus/property_converter.cc",
     "mus/property_utils.cc",
     "mus/system_input_injector_mus.cc",
@@ -399,7 +397,6 @@
     "mus/focus_synchronizer_unittest.cc",
     "mus/gesture_synchronizer_unittest.cc",
     "mus/input_method_mus_unittest.cc",
-    "mus/os_exchange_data_provider_mus_unittest.cc",
     "mus/property_converter_unittest.cc",
     "mus/user_activity_forwarder_unittest.cc",
     "mus/window_port_mus_unittest.cc",
@@ -411,6 +408,7 @@
     "window_occlusion_change_builder_unittest.cc",
     "window_occlusion_tracker_unittest.cc",
     "window_targeter_unittest.cc",
+    "window_tree_host_platform_unittest.cc",
     "window_tree_host_unittest.cc",
     "window_unittest.cc",
   ]
diff --git a/ui/aura/env.cc b/ui/aura/env.cc
index 81a4212..31f4221 100644
--- a/ui/aura/env.cc
+++ b/ui/aura/env.cc
@@ -17,7 +17,6 @@
 #include "ui/aura/local/window_port_local.h"
 #include "ui/aura/mouse_location_manager.h"
 #include "ui/aura/mus/mus_types.h"
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
 #include "ui/aura/mus/system_input_injector_mus.h"
 #include "ui/aura/mus/window_port_mus.h"
 #include "ui/aura/mus/window_tree_client.h"
@@ -90,8 +89,6 @@
 // Env, public:
 
 Env::~Env() {
-  if (is_os_exchange_data_provider_factory_)
-    ui::OSExchangeDataProviderFactory::SetFactory(nullptr);
   if (is_override_input_injector_factory_)
     ui::SetSystemInputInjectorFactory(nullptr);
 
@@ -334,7 +331,6 @@
 
 void Env::Init(service_manager::Connector* connector) {
   if (mode_ == Mode::MUS) {
-    EnableMusOSExchangeDataProvider();
     EnableMusOverrideInputInjector();
     // Remote clients should not throttle, only the window-service should
     // throttle (which corresponds to Mode::LOCAL).
@@ -369,13 +365,6 @@
     event_source_ = ui::PlatformEventSource::CreateDefault();
 }
 
-void Env::EnableMusOSExchangeDataProvider() {
-  if (!is_os_exchange_data_provider_factory_) {
-    ui::OSExchangeDataProviderFactory::SetFactory(this);
-    is_os_exchange_data_provider_factory_ = true;
-  }
-}
-
 void Env::EnableMusOverrideInputInjector() {
   if (!is_override_input_injector_factory_) {
     ui::SetSystemInputInjectorFactory(this);
@@ -423,10 +412,6 @@
   return nullptr;
 }
 
-std::unique_ptr<ui::OSExchangeData::Provider> Env::BuildProvider() {
-  return std::make_unique<aura::OSExchangeDataProviderMus>();
-}
-
 std::unique_ptr<ui::SystemInputInjector> Env::CreateSystemInputInjector() {
   return std::make_unique<SystemInputInjectorMus>(
       window_tree_client_ ? window_tree_client_->connector() : nullptr);
diff --git a/ui/aura/env.h b/ui/aura/env.h
index 03cd3df8..c16a692 100644
--- a/ui/aura/env.h
+++ b/ui/aura/env.h
@@ -15,7 +15,6 @@
 #include "build/build_config.h"
 #include "mojo/public/cpp/system/buffer.h"
 #include "ui/aura/aura_export.h"
-#include "ui/base/dragdrop/os_exchange_data_provider_factory.h"
 #include "ui/events/event_target.h"
 #include "ui/events/system_input_injector.h"
 #include "ui/gfx/geometry/point.h"
@@ -68,7 +67,6 @@
 
 // A singleton object that tracks general state within Aura.
 class AURA_EXPORT Env : public ui::EventTarget,
-                        public ui::OSExchangeDataProviderFactory::Factory,
                         public ui::SystemInputInjectorFactory,
                         public base::SupportsUserData {
  public:
@@ -230,11 +228,6 @@
 
   void Init(service_manager::Connector* connector);
 
-  // After calling this method, all OSExchangeDataProvider instances will be
-  // Mus instances. We can't do this work in Init(), because our mode may
-  // changed via the EnvTestHelper.
-  void EnableMusOSExchangeDataProvider();
-
   // After calling this method, all SystemInputInjectors will go through mus
   // instead of ozone.
   void EnableMusOverrideInputInjector();
@@ -253,9 +246,6 @@
   std::unique_ptr<ui::EventTargetIterator> GetChildIterator() const override;
   ui::EventTargeter* GetEventTargeter() override;
 
-  // Overridden from ui::OSExchangeDataProviderFactory::Factory:
-  std::unique_ptr<ui::OSExchangeData::Provider> BuildProvider() override;
-
   // Overridden from SystemInputInjectorFactory:
   std::unique_ptr<ui::SystemInputInjector> CreateSystemInputInjector() override;
 
@@ -289,8 +279,6 @@
   // This may be set to true in tests to force using |last_mouse_location_|
   // rather than querying WindowTreeClient.
   bool always_use_last_mouse_location_ = false;
-  // Whether we set ourselves as the OSExchangeDataProviderFactory.
-  bool is_os_exchange_data_provider_factory_ = false;
   // Whether we set ourselves as the SystemInputInjectorFactory.
   bool is_override_input_injector_factory_ = false;
 
diff --git a/ui/aura/mus/drag_drop_controller_mus.cc b/ui/aura/mus/drag_drop_controller_mus.cc
index 2c68ff66..a2934e52 100644
--- a/ui/aura/mus/drag_drop_controller_mus.cc
+++ b/ui/aura/mus/drag_drop_controller_mus.cc
@@ -19,7 +19,6 @@
 #include "ui/aura/env.h"
 #include "ui/aura/mus/drag_drop_controller_host.h"
 #include "ui/aura/mus/mus_types.h"
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
 #include "ui/aura/mus/window_mus.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
@@ -79,8 +78,7 @@
 
 void DragDropControllerMus::OnDragDropStart(
     std::map<std::string, std::vector<uint8_t>> data) {
-  os_exchange_data_ = std::make_unique<ui::OSExchangeData>(
-      std::make_unique<aura::OSExchangeDataProviderMus>(std::move(data)));
+  os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
 }
 
 uint32_t DragDropControllerMus::OnDragEnter(WindowMus* window,
@@ -188,17 +186,15 @@
     current_drag_state.source_window_tracker.Add(source_window);
   }
 
-  std::map<std::string, std::vector<uint8_t>> drag_data =
-      static_cast<const aura::OSExchangeDataProviderMus&>(data.provider())
-          .GetData();
+  std::map<std::string, std::vector<uint8_t>> drag_data;
 
   for (client::DragDropClientObserver& observer : observers_)
     observer.OnDragStarted();
 
-  window_tree_->PerformDragDrop(
-      change_id, source_window_mus->server_id(), screen_location,
-      mojo::MapToFlatMap(drag_data), data.provider().GetDragImage(),
-      data.provider().GetDragImageOffset(), drag_operations, mojo_source);
+  window_tree_->PerformDragDrop(change_id, source_window_mus->server_id(),
+                                screen_location, mojo::MapToFlatMap(drag_data),
+                                gfx::ImageSkia(), gfx::Vector2d(),
+                                drag_operations, mojo_source);
 
   run_loop.Run();
   return current_drag_state.completed_action;
diff --git a/ui/aura/mus/os_exchange_data_provider_mus.cc b/ui/aura/mus/os_exchange_data_provider_mus.cc
deleted file mode 100644
index 0c3ca11c..0000000
--- a/ui/aura/mus/os_exchange_data_provider_mus.cc
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/memory/ptr_util.h"
-#include "base/stl_util.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "net/base/filename_util.h"
-#include "ui/base/clipboard/clipboard_constants.h"
-#include "ui/base/clipboard/clipboard_format_type.h"
-#include "ui/base/dragdrop/file_info.h"
-#include "ui/base/mojo/clipboard.mojom.h"
-#include "url/gurl.h"
-
-namespace aura {
-
-namespace {
-
-std::vector<uint8_t> FromString(const std::string& str) {
-  return std::vector<uint8_t>(str.begin(), str.end());
-}
-
-std::string ToString(const std::vector<uint8_t>& v) {
-  return std::string(v.begin(), v.end());
-}
-
-base::string16 ToString16(const std::vector<uint8_t>& v) {
-  DCHECK_EQ(0u, v.size() % 2);
-  return base::string16(reinterpret_cast<const base::char16*>(v.data()),
-                        v.size() / 2);
-}
-
-std::vector<base::StringPiece> ParseURIList(const std::vector<uint8_t>& data) {
-  return base::SplitStringPiece(
-      base::StringPiece(reinterpret_cast<const char*>(&data.front()),
-                        data.size()),
-      "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-}
-
-void AddString16ToVector(const base::string16& str,
-                         std::vector<uint8_t>* bytes) {
-  const unsigned char* front = reinterpret_cast<const uint8_t*>(str.data());
-  bytes->insert(bytes->end(), front, front + (str.size() * 2));
-}
-
-}  // namespace
-
-OSExchangeDataProviderMus::OSExchangeDataProviderMus() {}
-
-OSExchangeDataProviderMus::OSExchangeDataProviderMus(Data data)
-    : mime_data_(std::move(data)) {}
-
-OSExchangeDataProviderMus::~OSExchangeDataProviderMus() {}
-
-OSExchangeDataProviderMus::Data OSExchangeDataProviderMus::GetData() const {
-  return mime_data_;
-}
-
-std::unique_ptr<ui::OSExchangeData::Provider> OSExchangeDataProviderMus::Clone()
-    const {
-  std::unique_ptr<OSExchangeDataProviderMus> r =
-      std::make_unique<OSExchangeDataProviderMus>();
-  r->drag_image_ = drag_image_;
-  r->drag_image_offset_ = drag_image_offset_;
-  r->mime_data_ = mime_data_;
-  return base::WrapUnique<ui::OSExchangeData::Provider>(r.release());
-}
-
-void OSExchangeDataProviderMus::MarkOriginatedFromRenderer() {
-  // Currently unimplemented because ChromeOS doesn't need this.
-  //
-  // TODO(erg): Implement this when we start porting mus to other platforms.
-}
-
-bool OSExchangeDataProviderMus::DidOriginateFromRenderer() const {
-  return false;
-}
-
-void OSExchangeDataProviderMus::SetString(const base::string16& data) {
-  if (HasString())
-    return;
-
-  mime_data_[ui::kMimeTypeText] = FromString(base::UTF16ToUTF8(data));
-}
-
-void OSExchangeDataProviderMus::SetURL(const GURL& url,
-                                       const base::string16& title) {
-  base::string16 spec = base::UTF8ToUTF16(url.spec());
-  std::vector<unsigned char> data;
-  AddString16ToVector(spec, &data);
-  AddString16ToVector(base::ASCIIToUTF16("\n"), &data);
-  AddString16ToVector(title, &data);
-  mime_data_[ui::kMimeTypeMozillaURL] = std::move(data);
-
-  if (!base::ContainsKey(mime_data_, ui::kMimeTypeText))
-    mime_data_[ui::kMimeTypeText] = FromString(url.spec());
-}
-
-void OSExchangeDataProviderMus::SetFilename(const base::FilePath& path) {
-  std::vector<ui::FileInfo> data;
-  data.push_back(ui::FileInfo(path, base::FilePath()));
-  SetFilenames(data);
-}
-
-void OSExchangeDataProviderMus::SetFilenames(
-    const std::vector<ui::FileInfo>& file_names) {
-  std::vector<std::string> paths;
-  for (auto it = file_names.begin(); it != file_names.end(); ++it) {
-    std::string url_spec = net::FilePathToFileURL(it->path).spec();
-    if (!url_spec.empty())
-      paths.push_back(url_spec);
-  }
-
-  std::string joined_data = base::JoinString(paths, "\n");
-  mime_data_[ui::kMimeTypeURIList] = FromString(joined_data);
-}
-
-void OSExchangeDataProviderMus::SetPickledData(
-    const ui::ClipboardFormatType& format,
-    const base::Pickle& pickle) {
-  const unsigned char* bytes =
-      reinterpret_cast<const unsigned char*>(pickle.data());
-
-  mime_data_[format.Serialize()] =
-      std::vector<uint8_t>(bytes, bytes + pickle.size());
-}
-
-bool OSExchangeDataProviderMus::GetString(base::string16* data) const {
-  auto it = mime_data_.find(ui::kMimeTypeText);
-  if (it != mime_data_.end())
-    *data = base::UTF8ToUTF16(ToString(it->second));
-  return it != mime_data_.end();
-}
-
-bool OSExchangeDataProviderMus::GetURLAndTitle(
-    ui::OSExchangeData::FilenameToURLPolicy policy,
-    GURL* url,
-    base::string16* title) const {
-  auto it = mime_data_.find(ui::kMimeTypeMozillaURL);
-  if (it == mime_data_.end()) {
-    title->clear();
-    return GetPlainTextURL(url) ||
-           (policy == ui::OSExchangeData::CONVERT_FILENAMES && GetFileURL(url));
-  }
-
-  base::string16 data = ToString16(it->second);
-  base::string16::size_type newline = data.find('\n');
-  if (newline == std::string::npos)
-    return false;
-
-  GURL unparsed_url(data.substr(0, newline));
-  if (!unparsed_url.is_valid())
-    return false;
-
-  *url = unparsed_url;
-  *title = data.substr(newline + 1);
-  return true;
-}
-
-bool OSExchangeDataProviderMus::GetFilename(base::FilePath* path) const {
-  std::vector<ui::FileInfo> filenames;
-  if (GetFilenames(&filenames)) {
-    *path = filenames.front().path;
-    return true;
-  }
-
-  return false;
-}
-
-bool OSExchangeDataProviderMus::GetFilenames(
-    std::vector<ui::FileInfo>* file_names) const {
-  auto it = mime_data_.find(ui::kMimeTypeURIList);
-  if (it == mime_data_.end())
-    return false;
-
-  file_names->clear();
-  for (const base::StringPiece& piece : ParseURIList(it->second)) {
-    GURL url(piece);
-    base::FilePath file_path;
-    if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
-      file_names->push_back(ui::FileInfo(file_path, base::FilePath()));
-  }
-
-  return true;
-}
-
-bool OSExchangeDataProviderMus::GetPickledData(
-    const ui::ClipboardFormatType& format,
-    base::Pickle* data) const {
-  auto it = mime_data_.find(format.Serialize());
-  if (it == mime_data_.end())
-    return false;
-
-  // Note that the pickle object on the right hand side of the assignment
-  // only refers to the bytes in |data|. The assignment copies the data.
-  *data = base::Pickle(reinterpret_cast<const char*>(it->second.data()),
-                       static_cast<int>(it->second.size()));
-  return true;
-}
-
-bool OSExchangeDataProviderMus::HasString() const {
-  return base::ContainsKey(mime_data_, ui::kMimeTypeText);
-}
-
-bool OSExchangeDataProviderMus::HasURL(
-    ui::OSExchangeData::FilenameToURLPolicy policy) const {
-  if (base::ContainsKey(mime_data_, ui::kMimeTypeMozillaURL))
-    return true;
-
-  auto it = mime_data_.find(ui::kMimeTypeURIList);
-  if (it == mime_data_.end())
-    return false;
-
-  for (const base::StringPiece& piece : ParseURIList(it->second)) {
-    if (!GURL(piece).SchemeIsFile() ||
-        policy == ui::OSExchangeData::CONVERT_FILENAMES) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool OSExchangeDataProviderMus::HasFile() const {
-  auto it = mime_data_.find(ui::kMimeTypeURIList);
-  if (it == mime_data_.end())
-    return false;
-
-  for (const base::StringPiece& piece : ParseURIList(it->second)) {
-    GURL url(piece);
-    base::FilePath file_path;
-    if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
-      return true;
-  }
-
-  return false;
-}
-
-bool OSExchangeDataProviderMus::HasCustomFormat(
-    const ui::ClipboardFormatType& format) const {
-  return base::ContainsKey(mime_data_, format.Serialize());
-}
-
-// These methods were added in an ad-hoc way to different operating
-// systems. We need to support them until they get cleaned up.
-// TODO(https://crbug.com/951547): Clean up OSExchangeDataProviderMus methods
-// that were added in an ad-hoc way
-#if defined(USE_X11) || defined(OS_WIN)
-void OSExchangeDataProviderMus::SetFileContents(
-    const base::FilePath& filename,
-    const std::string& file_contents) {}
-#endif
-
-#if defined(OS_WIN)
-bool OSExchangeDataProviderMus::GetFileContents(
-    base::FilePath* filename,
-    std::string* file_contents) const {
-  return false;
-}
-
-bool OSExchangeDataProviderMus::HasFileContents() const {
-  return false;
-}
-
-void OSExchangeDataProviderMus::SetVirtualFileContentsForTesting(
-    const std::vector<std::pair<base::FilePath, std::string>>&
-        filenames_and_contents,
-    DWORD tymed) {}
-
-bool OSExchangeDataProviderMus::HasVirtualFilenames() const {
-  return false;
-}
-
-bool OSExchangeDataProviderMus::GetVirtualFilenames(
-    std::vector<ui::FileInfo>* filenames) const {
-  return false;
-}
-
-bool OSExchangeDataProviderMus::GetVirtualFilesAsTempFiles(
-    base::OnceCallback<
-        void(const std::vector<std::pair<base::FilePath, base::FilePath>>&)>
-        callback) const {
-  return false;
-}
-
-void OSExchangeDataProviderMus::SetDownloadFileInfo(
-    const ui::OSExchangeData::DownloadFileInfo& download) {}
-#endif
-
-#if defined(USE_AURA)
-void OSExchangeDataProviderMus::SetHtml(const base::string16& html,
-                                        const GURL& base_url) {
-  std::vector<unsigned char> bytes;
-  // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
-  // assume UTF-8.
-  bytes.push_back(0xFF);
-  bytes.push_back(0xFE);
-  AddString16ToVector(html, &bytes);
-  mime_data_[ui::kMimeTypeHTML] = bytes;
-}
-
-bool OSExchangeDataProviderMus::GetHtml(base::string16* html,
-                                        GURL* base_url) const {
-  auto it = mime_data_.find(ui::kMimeTypeHTML);
-  if (it == mime_data_.end())
-    return false;
-
-  const unsigned char* data = it->second.data();
-  size_t size = it->second.size();
-  base::string16 markup;
-
-  // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is
-  // UTF-16, otherwise assume UTF-8.
-  if (size >= 2 && reinterpret_cast<const uint16_t*>(data)[0] == 0xFEFF) {
-    markup.assign(reinterpret_cast<const base::char16*>(data) + 1,
-                  (size / 2) - 1);
-  } else {
-    base::UTF8ToUTF16(reinterpret_cast<const char*>(data), size, &markup);
-  }
-
-  // If there is a terminating NULL, drop it.
-  if (!markup.empty() && markup.at(markup.length() - 1) == '\0')
-    markup.resize(markup.length() - 1);
-
-  *html = markup;
-  *base_url = GURL();
-  return true;
-}
-
-bool OSExchangeDataProviderMus::HasHtml() const {
-  return base::ContainsKey(mime_data_, ui::kMimeTypeHTML);
-}
-#endif
-
-#if defined(USE_AURA) || defined(OS_MACOSX)
-void OSExchangeDataProviderMus::SetDragImage(
-    const gfx::ImageSkia& image,
-    const gfx::Vector2d& cursor_offset) {
-  drag_image_ = image;
-  drag_image_offset_ = cursor_offset;
-}
-
-gfx::ImageSkia OSExchangeDataProviderMus::GetDragImage() const {
-  return drag_image_;
-}
-
-gfx::Vector2d OSExchangeDataProviderMus::GetDragImageOffset() const {
-  return drag_image_offset_;
-}
-#endif
-
-bool OSExchangeDataProviderMus::GetFileURL(GURL* url) const {
-  base::FilePath file_path;
-  if (!GetFilename(&file_path))
-    return false;
-
-  GURL test_url = net::FilePathToFileURL(file_path);
-  if (!test_url.is_valid())
-    return false;
-
-  if (url)
-    *url = test_url;
-  return true;
-}
-
-bool OSExchangeDataProviderMus::GetPlainTextURL(GURL* url) const {
-  base::string16 str;
-  if (!GetString(&str))
-    return false;
-
-  GURL test_url(str);
-  if (!test_url.is_valid())
-    return false;
-
-  if (url)
-    *url = test_url;
-  return true;
-}
-
-}  // namespace aura
diff --git a/ui/aura/mus/os_exchange_data_provider_mus.h b/ui/aura/mus/os_exchange_data_provider_mus.h
deleted file mode 100644
index 2915f32c..0000000
--- a/ui/aura/mus/os_exchange_data_provider_mus.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef UI_AURA_MUS_OS_EXCHANGE_DATA_PROVIDER_MUS_H_
-#define UI_AURA_MUS_OS_EXCHANGE_DATA_PROVIDER_MUS_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "ui/aura/aura_export.h"
-#include "ui/base/dragdrop/os_exchange_data.h"
-#include "ui/gfx/geometry/vector2d.h"
-#include "ui/gfx/image/image_skia.h"
-
-namespace aura {
-
-// Translates chrome's requests for various data types to a platform specific
-// type. In the case of mus, this is a mapping of MIME types to byte arrays.
-//
-// TODO(erg): In the long run, there's a lot of optimizations that we can do
-// once everything targets mus. The entire model of OSExchangeDataProvider
-// shoves all data across the wire at once whether it is needed or not.
-class AURA_EXPORT OSExchangeDataProviderMus
-    : public ui::OSExchangeData::Provider {
- public:
-  using Data = std::map<std::string, std::vector<uint8_t>>;
-
-  OSExchangeDataProviderMus();
-  explicit OSExchangeDataProviderMus(Data data);
-  ~OSExchangeDataProviderMus() override;
-
-  // Returns the raw MIME type to data mapping.
-  Data GetData() const;
-
-  // Overridden from OSExchangeData::Provider:
-  std::unique_ptr<Provider> Clone() const override;
-
-  void MarkOriginatedFromRenderer() override;
-  bool DidOriginateFromRenderer() const override;
-
-  void SetString(const base::string16& data) override;
-  void SetURL(const GURL& url, const base::string16& title) override;
-  void SetFilename(const base::FilePath& path) override;
-  void SetFilenames(const std::vector<ui::FileInfo>& file_names) override;
-  void SetPickledData(const ui::ClipboardFormatType& format,
-                      const base::Pickle& data) override;
-
-  bool GetString(base::string16* data) const override;
-  bool GetURLAndTitle(ui::OSExchangeData::FilenameToURLPolicy policy,
-                      GURL* url,
-                      base::string16* title) const override;
-  bool GetFilename(base::FilePath* path) const override;
-  bool GetFilenames(std::vector<ui::FileInfo>* file_names) const override;
-  bool GetPickledData(const ui::ClipboardFormatType& format,
-                      base::Pickle* data) const override;
-
-  bool HasString() const override;
-  bool HasURL(ui::OSExchangeData::FilenameToURLPolicy policy) const override;
-  bool HasFile() const override;
-  bool HasCustomFormat(const ui::ClipboardFormatType& format) const override;
-
-// Provider doesn't have a consistent interface between operating systems;
-// this wasn't seen as a problem when there was a single Provider subclass
-// per operating system. Now we have to have at least two providers per OS,
-// leading to the following warts, which will remain until we clean all the
-// callsites up.
-#if defined(USE_X11) || defined(OS_WIN)
-  void SetFileContents(const base::FilePath& filename,
-                       const std::string& file_contents) override;
-#endif
-#if defined(OS_WIN)
-  bool GetFileContents(base::FilePath* filename,
-                       std::string* file_contents) const override;
-  bool HasFileContents() const override;
-  void SetVirtualFileContentsForTesting(
-      const std::vector<std::pair<base::FilePath, std::string>>&
-          filenames_and_contents,
-      DWORD tymed) override;
-  bool HasVirtualFilenames() const override;
-  bool GetVirtualFilenames(std::vector<ui::FileInfo>* filenames) const override;
-  bool GetVirtualFilesAsTempFiles(
-      base::OnceCallback<
-          void(const std::vector<std::pair<base::FilePath, base::FilePath>>&)>
-          callback) const override;
-  void SetDownloadFileInfo(
-      const ui::OSExchangeData::DownloadFileInfo& download) override;
-#endif
-
-#if defined(USE_AURA)
-  void SetHtml(const base::string16& html, const GURL& base_url) override;
-  bool GetHtml(base::string16* html, GURL* base_url) const override;
-  bool HasHtml() const override;
-#endif
-
-#if defined(USE_AURA) || defined(OS_MACOSX)
-  void SetDragImage(const gfx::ImageSkia& image,
-                    const gfx::Vector2d& cursor_offset) override;
-  gfx::ImageSkia GetDragImage() const override;
-  gfx::Vector2d GetDragImageOffset() const override;
-#endif
-
- private:
-  // Returns true if |formats_| contains a file format and the file name can be
-  // parsed as a URL.
-  bool GetFileURL(GURL* url) const;
-
-  // Returns true if |formats_| contains a string format and the string can be
-  // parsed as a URL.
-  bool GetPlainTextURL(GURL* url) const;
-
-  // Drag image and offset data.
-  gfx::ImageSkia drag_image_;
-  gfx::Vector2d drag_image_offset_;
-
-  Data mime_data_;
-
-  DISALLOW_COPY_AND_ASSIGN(OSExchangeDataProviderMus);
-};
-
-}  // namespace aura
-
-#endif  // UI_AURA_MUS_OS_EXCHANGE_DATA_PROVIDER_MUS_H_
diff --git a/ui/aura/mus/os_exchange_data_provider_mus_unittest.cc b/ui/aura/mus/os_exchange_data_provider_mus_unittest.cc
deleted file mode 100644
index 4eea746..0000000
--- a/ui/aura/mus/os_exchange_data_provider_mus_unittest.cc
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ui/aura/mus/os_exchange_data_provider_mus.h"
-
-#include <memory>
-
-#include "base/files/file_util.h"
-#include "base/pickle.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/test/scoped_task_environment.h"
-#include "build/build_config.h"
-#include "net/base/filename_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/platform_test.h"
-#include "ui/base/clipboard/clipboard_format_type.h"
-#include "ui/base/dragdrop/os_exchange_data.h"
-#include "ui/base/dragdrop/os_exchange_data_provider_factory.h"
-#include "ui/events/platform/platform_event_source.h"
-#include "url/gurl.h"
-
-using ui::OSExchangeData;
-
-namespace aura {
-
-// This file is a copy/paste of the unit tests in os_exchange_data_unittest.cc,
-// with additional SetUp() to force the mus override. I thought about changeing
-// the OSExchangeData test suite to a parameterized test suite, but I've
-// previously had problems with that feature of gtest.
-
-class OSExchangeDataProviderMusTest
-    : public PlatformTest,
-      public ui::OSExchangeDataProviderFactory::Factory {
- public:
-  OSExchangeDataProviderMusTest()
-      : scoped_task_environment_(
-            base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
-
-  // Overridden from PlatformTest:
-  void SetUp() override {
-    PlatformTest::SetUp();
-    old_factory_ = ui::OSExchangeDataProviderFactory::TakeFactory();
-    ui::OSExchangeDataProviderFactory::SetFactory(this);
-  }
-
-  void TearDown() override {
-    ui::OSExchangeDataProviderFactory::TakeFactory();
-    ui::OSExchangeDataProviderFactory::SetFactory(old_factory_);
-    PlatformTest::TearDown();
-  }
-
-  // Overridden from ui::OSExchangeDataProviderFactory::Factory:
-  std::unique_ptr<OSExchangeData::Provider> BuildProvider() override {
-    return std::make_unique<OSExchangeDataProviderMus>();
-  }
-
- private:
-  ui::OSExchangeDataProviderFactory::Factory* old_factory_ = nullptr;
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-};
-
-TEST_F(OSExchangeDataProviderMusTest, StringDataGetAndSet) {
-  OSExchangeData data;
-  base::string16 input = base::ASCIIToUTF16("I can has cheezburger?");
-  EXPECT_FALSE(data.HasString());
-  data.SetString(input);
-  EXPECT_TRUE(data.HasString());
-
-  OSExchangeData data2(
-      std::unique_ptr<OSExchangeData::Provider>(data.provider().Clone()));
-  base::string16 output;
-  EXPECT_TRUE(data2.HasString());
-  EXPECT_TRUE(data2.GetString(&output));
-  EXPECT_EQ(input, output);
-  std::string url_spec = "http://www.goats.com/";
-  GURL url(url_spec);
-  base::string16 title;
-  EXPECT_FALSE(data2.GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES,
-                                    &url, &title));
-  // No URLs in |data|, so url should be untouched.
-  EXPECT_EQ(url_spec, url.spec());
-}
-
-TEST_F(OSExchangeDataProviderMusTest, TestURLExchangeFormats) {
-  OSExchangeData data;
-  std::string url_spec = "http://www.google.com/";
-  GURL url(url_spec);
-  base::string16 url_title = base::ASCIIToUTF16("www.google.com");
-  EXPECT_FALSE(data.HasURL(OSExchangeData::DO_NOT_CONVERT_FILENAMES));
-  data.SetURL(url, url_title);
-  EXPECT_TRUE(data.HasURL(OSExchangeData::DO_NOT_CONVERT_FILENAMES));
-
-  OSExchangeData data2(
-      std::unique_ptr<OSExchangeData::Provider>(data.provider().Clone()));
-
-  // URL spec and title should match
-  GURL output_url;
-  base::string16 output_title;
-  EXPECT_TRUE(data2.HasURL(OSExchangeData::DO_NOT_CONVERT_FILENAMES));
-  EXPECT_TRUE(data2.GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES,
-                                   &output_url, &output_title));
-  EXPECT_EQ(url_spec, output_url.spec());
-  EXPECT_EQ(url_title, output_title);
-  base::string16 output_string;
-
-  // URL should be the raw text response
-  EXPECT_TRUE(data2.GetString(&output_string));
-  EXPECT_EQ(url_spec, base::UTF16ToUTF8(output_string));
-}
-
-// Test that setting the URL does not overwrite a previously set custom string.
-TEST_F(OSExchangeDataProviderMusTest, URLAndString) {
-  OSExchangeData data;
-  base::string16 string = base::ASCIIToUTF16("I can has cheezburger?");
-  data.SetString(string);
-  std::string url_spec = "http://www.google.com/";
-  GURL url(url_spec);
-  base::string16 url_title = base::ASCIIToUTF16("www.google.com");
-  data.SetURL(url, url_title);
-
-  base::string16 output_string;
-  EXPECT_TRUE(data.GetString(&output_string));
-  EXPECT_EQ(string, output_string);
-
-  GURL output_url;
-  base::string16 output_title;
-  EXPECT_TRUE(data.GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES,
-                                  &output_url, &output_title));
-  EXPECT_EQ(url_spec, output_url.spec());
-  EXPECT_EQ(url_title, output_title);
-}
-
-TEST_F(OSExchangeDataProviderMusTest, TestFileToURLConversion) {
-  OSExchangeData data;
-  EXPECT_FALSE(data.HasURL(OSExchangeData::DO_NOT_CONVERT_FILENAMES));
-  EXPECT_FALSE(data.HasURL(OSExchangeData::CONVERT_FILENAMES));
-  EXPECT_FALSE(data.HasFile());
-
-  base::FilePath current_directory;
-  ASSERT_TRUE(base::GetCurrentDirectory(&current_directory));
-
-  data.SetFilename(current_directory);
-
-  {
-    EXPECT_FALSE(data.HasURL(OSExchangeData::DO_NOT_CONVERT_FILENAMES));
-    GURL actual_url;
-    base::string16 actual_title;
-    EXPECT_FALSE(data.GetURLAndTitle(OSExchangeData::DO_NOT_CONVERT_FILENAMES,
-                                     &actual_url, &actual_title));
-    EXPECT_EQ(GURL(), actual_url);
-    EXPECT_EQ(base::string16(), actual_title);
-  }
-
-  {
-    EXPECT_TRUE(data.HasURL(OSExchangeData::CONVERT_FILENAMES));
-    GURL actual_url;
-    base::string16 actual_title;
-    EXPECT_TRUE(data.GetURLAndTitle(OSExchangeData::CONVERT_FILENAMES,
-                                    &actual_url, &actual_title));
-    // Some Mac OS versions return the URL in file://localhost form instead
-    // of file:///, so we compare the url's path not its absolute string.
-    EXPECT_EQ(net::FilePathToFileURL(current_directory).path(),
-              actual_url.path());
-    EXPECT_EQ(base::string16(), actual_title);
-  }
-  EXPECT_TRUE(data.HasFile());
-  base::FilePath actual_path;
-  EXPECT_TRUE(data.GetFilename(&actual_path));
-  EXPECT_EQ(current_directory, actual_path);
-}
-
-TEST_F(OSExchangeDataProviderMusTest, TestPickledData) {
-  const ui::ClipboardFormatType kTestFormat =
-      ui::ClipboardFormatType::GetType("application/vnd.chromium.test");
-
-  base::Pickle saved_pickle;
-  saved_pickle.WriteInt(1);
-  saved_pickle.WriteInt(2);
-  OSExchangeData data;
-  data.SetPickledData(kTestFormat, saved_pickle);
-
-  OSExchangeData copy(
-      std::unique_ptr<OSExchangeData::Provider>(data.provider().Clone()));
-  EXPECT_TRUE(copy.HasCustomFormat(kTestFormat));
-
-  base::Pickle restored_pickle;
-  EXPECT_TRUE(copy.GetPickledData(kTestFormat, &restored_pickle));
-  base::PickleIterator iterator(restored_pickle);
-  int value;
-  EXPECT_TRUE(iterator.ReadInt(&value));
-  EXPECT_EQ(1, value);
-  EXPECT_TRUE(iterator.ReadInt(&value));
-  EXPECT_EQ(2, value);
-}
-
-TEST_F(OSExchangeDataProviderMusTest, TestHTML) {
-  OSExchangeData data;
-  GURL url("http://www.google.com/");
-  base::string16 html = base::ASCIIToUTF16(
-      "<HTML>\n<BODY>\n"
-      "<b>bold.</b> <i><b>This is bold italic.</b></i>\n"
-      "</BODY>\n</HTML>");
-  data.SetHtml(html, url);
-
-  OSExchangeData copy(
-      std::unique_ptr<OSExchangeData::Provider>(data.provider().Clone()));
-  base::string16 read_html;
-  EXPECT_TRUE(copy.GetHtml(&read_html, &url));
-  EXPECT_EQ(html, read_html);
-}
-
-}  // namespace aura
diff --git a/ui/aura/test/env_test_helper.h b/ui/aura/test/env_test_helper.h
index d63a8c8..796cc31 100644
--- a/ui/aura/test/env_test_helper.h
+++ b/ui/aura/test/env_test_helper.h
@@ -54,8 +54,6 @@
   Env::Mode SetMode(Env::Mode mode) {
     const Env::Mode old_mode = env_->mode_;
     env_->mode_ = mode;
-    if (mode == Env::Mode::MUS)
-      env_->EnableMusOSExchangeDataProvider();
     env_->in_mus_shutdown_ = false;
     return old_mode;
   }
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc
index 62b0d309..dbc486a 100644
--- a/ui/aura/window_tree_host_platform.cc
+++ b/ui/aura/window_tree_host_platform.cc
@@ -199,8 +199,14 @@
 }
 
 void WindowTreeHostPlatform::OnBoundsChanged(const gfx::Rect& new_bounds) {
-  for (WindowTreeHostObserver& observer : observers())
-    observer.OnHostWillProcessBoundsChange(this);
+  // It's possible this function may be called recursively. Only notify
+  // observers on initial entry. This way observers can safely assume that
+  // OnHostDidProcessBoundsChange() is called when all bounds changes have
+  // completed.
+  if (++on_bounds_changed_recursion_depth_ == 1) {
+    for (WindowTreeHostObserver& observer : observers())
+      observer.OnHostWillProcessBoundsChange(this);
+  }
   float current_scale = compositor()->device_scale_factor();
   float new_scale = ui::GetScaleFactorForNativeView(window());
   gfx::Rect old_bounds = bounds_in_pixels_;
@@ -218,8 +224,11 @@
     OnHostResizedInPixels(bounds_in_pixels_.size(),
                           local_surface_id_allocation);
   }
-  for (WindowTreeHostObserver& observer : observers())
-    observer.OnHostDidProcessBoundsChange(this);
+  DCHECK_GT(on_bounds_changed_recursion_depth_, 0);
+  if (--on_bounds_changed_recursion_depth_ == 0) {
+    for (WindowTreeHostObserver& observer : observers())
+      observer.OnHostDidProcessBoundsChange(this);
+  }
 }
 
 void WindowTreeHostPlatform::OnDamageRect(const gfx::Rect& damage_rect) {
diff --git a/ui/aura/window_tree_host_platform.h b/ui/aura/window_tree_host_platform.h
index 369c37b..2c86ab26 100644
--- a/ui/aura/window_tree_host_platform.h
+++ b/ui/aura/window_tree_host_platform.h
@@ -106,6 +106,11 @@
   viz::LocalSurfaceIdAllocation pending_local_surface_id_allocation_;
   gfx::Size pending_size_;
 
+  // Tracks how nested OnBoundsChanged() is. That is, on entering
+  // OnBoundsChanged() this is incremented and on leaving OnBoundsChanged() this
+  // is decremented.
+  int on_bounds_changed_recursion_depth_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(WindowTreeHostPlatform);
 };
 
diff --git a/ui/aura/window_tree_host_platform_unittest.cc b/ui/aura/window_tree_host_platform_unittest.cc
new file mode 100644
index 0000000..8d4b9d4
--- /dev/null
+++ b/ui/aura/window_tree_host_platform_unittest.cc
@@ -0,0 +1,97 @@
+// 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 "ui/aura/window_tree_host_platform.h"
+
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/window_tree_host_observer.h"
+#include "ui/platform_window/stub/stub_window.h"
+
+namespace aura {
+namespace {
+
+using WindowTreeHostPlatformTest = test::AuraTestBase;
+
+// Trivial WindowTreeHostPlatform implementation that installs a StubWindow as
+// the PlatformWindow.
+class TestWindowTreeHost : public WindowTreeHostPlatform {
+ public:
+  TestWindowTreeHost() {
+    SetPlatformWindow(std::make_unique<ui::StubWindow>(this));
+    CreateCompositor();
+  }
+
+  ui::PlatformWindow* platform_window() {
+    return WindowTreeHostPlatform::platform_window();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestWindowTreeHost);
+};
+
+// WindowTreeHostObserver that tracks calls to
+// OnHostWill/DidProcessBoundsChange. Additionally, this triggers a bounds
+// change from within OnHostResized(). Such a scenario happens in production
+// code.
+class TestWindowTreeHostObserver : public aura::WindowTreeHostObserver {
+ public:
+  TestWindowTreeHostObserver(WindowTreeHostPlatform* host,
+                             ui::PlatformWindow* platform_window)
+      : host_(host), platform_window_(platform_window) {
+    host_->AddObserver(this);
+  }
+  ~TestWindowTreeHostObserver() override { host_->RemoveObserver(this); }
+
+  int on_host_did_process_bounds_change_count() const {
+    return on_host_did_process_bounds_change_count_;
+  }
+
+  int on_host_will_process_bounds_change_count() const {
+    return on_host_will_process_bounds_change_count_;
+  }
+
+  // aura::WindowTreeHostObserver:
+  void OnHostResized(WindowTreeHost* host) override {
+    if (!should_change_bounds_in_on_resized_)
+      return;
+
+    should_change_bounds_in_on_resized_ = false;
+    gfx::Rect bounds = platform_window_->GetBounds();
+    bounds.set_x(bounds.x() + 1);
+    host_->SetBoundsInPixels(bounds, viz::LocalSurfaceIdAllocation());
+  }
+  void OnHostWillProcessBoundsChange(WindowTreeHost* host) override {
+    ++on_host_will_process_bounds_change_count_;
+  }
+  void OnHostDidProcessBoundsChange(WindowTreeHost* host) override {
+    ++on_host_did_process_bounds_change_count_;
+  }
+
+ private:
+  WindowTreeHostPlatform* host_;
+  ui::PlatformWindow* platform_window_;
+  bool should_change_bounds_in_on_resized_ = true;
+  int on_host_will_process_bounds_change_count_ = 0;
+  int on_host_did_process_bounds_change_count_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(TestWindowTreeHostObserver);
+};
+
+// Regression test for https://crbug.com/958449
+TEST_F(WindowTreeHostPlatformTest, HostWillProcessBoundsChangeRecursion) {
+  TestWindowTreeHost host;
+  TestWindowTreeHostObserver observer(&host, host.platform_window());
+  // This call triggers a recursive bounds change. That is, this results in
+  // WindowTreePlatform::OnBoundsChanged() indirectly calling back into
+  // WindowTreePlatform::OnBoundsChanged(). In such a scenario the observer
+  // should be notified only once (see comment in
+  // WindowTreeHostPlatform::OnBoundsChanged() for details).
+  host.SetBoundsInPixels(gfx::Rect(1, 2, 3, 4),
+                         viz::LocalSurfaceIdAllocation());
+  EXPECT_EQ(1, observer.on_host_did_process_bounds_change_count());
+  EXPECT_EQ(1, observer.on_host_will_process_bounds_change_count());
+}
+
+}  // namespace
+}  // namespace aura
diff --git a/ui/base/dragdrop/os_exchange_data_provider_factory.cc b/ui/base/dragdrop/os_exchange_data_provider_factory.cc
index 9c3a9ab2..30a259cb1d 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_factory.cc
+++ b/ui/base/dragdrop/os_exchange_data_provider_factory.cc
@@ -18,28 +18,9 @@
 
 namespace ui {
 
-OSExchangeDataProviderFactory::Factory* factory_ = nullptr;
-
-// static
-void OSExchangeDataProviderFactory::SetFactory(Factory* factory) {
-  DCHECK(!factory_ || !factory);
-  factory_ = factory;
-}
-
-// static
-OSExchangeDataProviderFactory::Factory*
-OSExchangeDataProviderFactory::TakeFactory() {
-  OSExchangeDataProviderFactory::Factory* to_return = factory_;
-  factory_ = nullptr;
-  return to_return;
-}
-
 //static
 std::unique_ptr<OSExchangeData::Provider>
 OSExchangeDataProviderFactory::CreateProvider() {
-  if (factory_)
-    return factory_->BuildProvider();
-
 #if defined(USE_X11)
   return std::make_unique<OSExchangeDataProviderAuraX11>();
 #elif defined(OS_LINUX)
diff --git a/ui/base/dragdrop/os_exchange_data_provider_factory.h b/ui/base/dragdrop/os_exchange_data_provider_factory.h
index 4029d08..626b7ea 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_factory.h
+++ b/ui/base/dragdrop/os_exchange_data_provider_factory.h
@@ -12,23 +12,10 @@
 
 namespace ui {
 
-// Builds OSExchangeDataProviders. We need to be able to switch providers at
-// runtime based on the configuration flags. If no factory is set,
-// CreateProvider() will default to the current operating system's default.
+// Builds platform specific OSExchangeDataProviders.
 class UI_BASE_EXPORT OSExchangeDataProviderFactory {
  public:
-  class Factory {
-   public:
-    virtual std::unique_ptr<OSExchangeData::Provider> BuildProvider() = 0;
-  };
-
-  // Sets the factory which builds the providers.
-  static void SetFactory(Factory* factory);
-
-  // Returns the current factory and sets the factory to null.
-  static Factory* TakeFactory();
-
-  // Creates a Provider based on the current configuration.
+  // Creates a Provider based on the current platform.
   static std::unique_ptr<OSExchangeData::Provider> CreateProvider();
 };
 
diff --git a/ui/base/x/x11_display_util.cc b/ui/base/x/x11_display_util.cc
index e34c5cc..0cd4650c 100644
--- a/ui/base/x/x11_display_util.cc
+++ b/ui/base/x/x11_display_util.cc
@@ -232,7 +232,7 @@
         gfx::ICCProfile icc_profile = ui::GetICCProfileForMonitor(
             monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second);
         icc_profile.HistogramDisplay(display.id());
-        display.set_color_space(icc_profile.GetColorSpace());
+        display.set_color_space(icc_profile.GetPrimariesOnlyColorSpace());
       }
 
       displays.push_back(display);
diff --git a/ui/display/win/color_profile_reader.cc b/ui/display/win/color_profile_reader.cc
index 80ae93e39..a2b4f1b4 100644
--- a/ui/display/win/color_profile_reader.cc
+++ b/ui/display/win/color_profile_reader.cc
@@ -121,7 +121,7 @@
     icc_profile = found->second;
   if (has_read_profiles_)
     icc_profile.HistogramDisplay(display_id);
-  return icc_profile.IsValid() ? icc_profile.GetColorSpace()
+  return icc_profile.IsValid() ? icc_profile.GetPrimariesOnlyColorSpace()
                                : gfx::ColorSpace::CreateSRGB();
 }
 
diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc
index f71c8e2..b8b92f19 100644
--- a/ui/events/blink/blink_event_util.cc
+++ b/ui/events/blink/blink_event_util.cc
@@ -787,7 +787,7 @@
       gesture.data.scroll_begin.delta_x_hint = details.scroll_x_hint();
       gesture.data.scroll_begin.delta_y_hint = details.scroll_y_hint();
       gesture.data.scroll_begin.delta_hint_units =
-          static_cast<blink::WebGestureEvent::ScrollUnits>(
+          static_cast<blink::WebScrollGranularity>(
               details.scroll_begin_units());
       break;
     case ET_GESTURE_SCROLL_UPDATE:
@@ -795,7 +795,7 @@
       gesture.data.scroll_update.delta_x = details.scroll_x();
       gesture.data.scroll_update.delta_y = details.scroll_y();
       gesture.data.scroll_update.delta_units =
-          static_cast<blink::WebGestureEvent::ScrollUnits>(
+          static_cast<blink::WebScrollGranularity>(
               details.scroll_update_units());
       break;
     case ET_GESTURE_SCROLL_END:
@@ -910,15 +910,19 @@
         (gesture_event->PositionInWidget().y + delta.y()) * scale));
     switch (gesture_event->GetType()) {
       case blink::WebInputEvent::kGestureScrollUpdate:
-        if (gesture_event->data.scroll_update.delta_units !=
-            blink::WebGestureEvent::ScrollUnits::kPage) {
+        if (gesture_event->data.scroll_update.delta_units ==
+                blink::WebScrollGranularity::kScrollByPixel ||
+            gesture_event->data.scroll_update.delta_units ==
+                blink::WebScrollGranularity::kScrollByPrecisePixel) {
           gesture_event->data.scroll_update.delta_x *= scale;
           gesture_event->data.scroll_update.delta_y *= scale;
         }
         break;
       case blink::WebInputEvent::kGestureScrollBegin:
-        if (gesture_event->data.scroll_begin.delta_hint_units !=
-            blink::WebGestureEvent::ScrollUnits::kPage) {
+        if (gesture_event->data.scroll_begin.delta_hint_units ==
+                blink::WebScrollGranularity::kScrollByPixel ||
+            gesture_event->data.scroll_begin.delta_hint_units ==
+                blink::WebScrollGranularity::kScrollByPrecisePixel) {
           gesture_event->data.scroll_begin.delta_x_hint *= scale;
           gesture_event->data.scroll_begin.delta_y_hint *= scale;
         }
diff --git a/ui/events/blink/blink_event_util_unittest.cc b/ui/events/blink/blink_event_util_unittest.cc
index b720f29..7d46eefe 100644
--- a/ui/events/blink/blink_event_util_unittest.cc
+++ b/ui/events/blink/blink_event_util_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ui/events/blink/blink_event_util.h"
 
+#include "base/stl_util.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_gesture_event.h"
@@ -131,6 +132,44 @@
   EXPECT_EQ(1.f, gestureEvent->data.scroll_update.delta_y);
 }
 
+TEST(BlinkEventUtilTest, LineAndDocumentScrollEvents) {
+  static const ui::EventType types[] = {
+      ui::ET_GESTURE_SCROLL_BEGIN,
+      ui::ET_GESTURE_SCROLL_UPDATE,
+  };
+
+  static const ui::GestureEventDetails::ScrollUnits units[] = {
+      ui::GestureEventDetails::ScrollUnits::LINE,
+      ui::GestureEventDetails::ScrollUnits::DOCUMENT,
+  };
+
+  for (size_t i = 0; i < base::size(types); i++) {
+    ui::EventType type = types[i];
+    for (size_t j = 0; j < base::size(units); j++) {
+      ui::GestureEventDetails::ScrollUnits unit = units[j];
+      ui::GestureEventDetails details(type, 1, 1, unit);
+      details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
+      auto event = CreateWebGestureEvent(details, base::TimeTicks(),
+                                         gfx::PointF(1.f, 1.f),
+                                         gfx::PointF(1.f, 1.f), 0, 0U);
+      std::unique_ptr<blink::WebInputEvent> webEvent =
+          ScaleWebInputEvent(event, 2.f);
+      EXPECT_TRUE(webEvent);
+      blink::WebGestureEvent* gestureEvent =
+          static_cast<blink::WebGestureEvent*>(webEvent.get());
+      // Line and document based scroll events should not be scaled.
+      if (type == ui::ET_GESTURE_SCROLL_BEGIN) {
+        EXPECT_EQ(1.f, gestureEvent->data.scroll_begin.delta_x_hint);
+        EXPECT_EQ(1.f, gestureEvent->data.scroll_begin.delta_y_hint);
+      } else {
+        EXPECT_TRUE(type == ui::ET_GESTURE_SCROLL_UPDATE);
+        EXPECT_EQ(1.f, gestureEvent->data.scroll_update.delta_x);
+        EXPECT_EQ(1.f, gestureEvent->data.scroll_update.delta_y);
+      }
+    }
+  }
+}
+
 TEST(BlinkEventUtilTest, TouchEventCoalescing) {
   blink::WebTouchPoint touch_point;
   touch_point.id = 1;
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index 4dad264..ad3d35a6 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -354,7 +354,7 @@
     synthetic_gesture_event->data.scroll_begin.inertial_phase =
         WebGestureEvent::kNonMomentumPhase;
     synthetic_gesture_event->data.scroll_begin.delta_hint_units =
-        WebGestureEvent::kPixels;
+        blink::WebScrollGranularity::kScrollByPixel;
   } else if (type == WebInputEvent::Type::kGestureScrollUpdate) {
     synthetic_gesture_event->data.scroll_update.delta_x =
         -pointer_result.scroll_offset.x();
@@ -363,7 +363,7 @@
     synthetic_gesture_event->data.scroll_update.inertial_phase =
         WebGestureEvent::kNonMomentumPhase;
     synthetic_gesture_event->data.scroll_update.delta_units =
-        WebGestureEvent::kPixels;
+        blink::WebScrollGranularity::kScrollByPixel;
   }
 
   synthetic_gesture_event->SetPositionInWidget(position_in_widget);
@@ -679,7 +679,7 @@
   cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event);
   cc::InputHandler::ScrollStatus scroll_status;
   if (gesture_event.data.scroll_begin.delta_hint_units ==
-      blink::WebGestureEvent::ScrollUnits::kPage) {
+      blink::WebScrollGranularity::kScrollByPage) {
     scroll_status.thread = cc::InputHandler::SCROLL_ON_MAIN_THREAD;
     scroll_status.main_thread_scrolling_reasons =
         cc::MainThreadScrollingReason::kContinuingMainThreadScroll;
@@ -687,7 +687,7 @@
     scroll_status = input_handler_->RootScrollBegin(
         &scroll_state, GestureScrollInputType(gesture_event.SourceDevice()));
   } else if (ShouldAnimate(gesture_event.data.scroll_begin.delta_hint_units !=
-                           blink::WebGestureEvent::ScrollUnits::kPixels)) {
+                           blink::WebScrollGranularity::kScrollByPixel)) {
     DCHECK(!scroll_state.is_in_inertial_phase());
     scroll_status = input_handler_->ScrollAnimatedBegin(&scroll_state);
   } else {
@@ -756,7 +756,7 @@
   gfx::PointF scroll_point(gesture_event.PositionInWidget());
 
   if (ShouldAnimate(gesture_event.data.scroll_update.delta_units !=
-                    blink::WebGestureEvent::ScrollUnits::kPixels)) {
+                    blink::WebScrollGranularity::kScrollByPixel)) {
     DCHECK(!scroll_state.is_in_inertial_phase());
     base::TimeTicks event_time = gesture_event.TimeStamp();
     base::TimeDelta delay = base::TimeTicks::Now() - event_time;
@@ -907,6 +907,14 @@
     }
   }
 
+  // Depending on which arm of the SkipTouchEventFilter experiment we're on, we
+  // may need to simulate a passive listener instead of dropping touch events.
+  if (result == DROP_EVENT &&
+      (skip_touch_filter_all_ ||
+       (skip_touch_filter_discrete_ &&
+        touch_event.GetType() == WebInputEvent::kTouchStart)))
+    result = DID_HANDLE_NON_BLOCKING;
+
   // Merge |touch_result_| and |result| so the result has the highest
   // priority value according to the sequence; (DROP_EVENT,
   // DID_HANDLE_NON_BLOCKING, DID_NOT_HANDLE).
diff --git a/ui/events/blink/input_handler_proxy_unittest.cc b/ui/events/blink/input_handler_proxy_unittest.cc
index a114c1b..2451e6df 100644
--- a/ui/events/blink/input_handler_proxy_unittest.cc
+++ b/ui/events/blink/input_handler_proxy_unittest.cc
@@ -730,7 +730,7 @@
 
   gesture_.SetType(WebInputEvent::kGestureScrollBegin);
   gesture_.data.scroll_begin.delta_hint_units =
-      WebGestureEvent::ScrollUnits::kPage;
+      blink::WebScrollGranularity::kScrollByPage;
   EXPECT_EQ(expected_disposition_,
             input_handler_->RouteToTypeSpecificHandler(gesture_));
 
@@ -738,7 +738,8 @@
 
   gesture_.SetType(WebInputEvent::kGestureScrollUpdate);
   gesture_.data.scroll_update.delta_y = 1;
-  gesture_.data.scroll_update.delta_units = WebGestureEvent::ScrollUnits::kPage;
+  gesture_.data.scroll_update.delta_units =
+      blink::WebScrollGranularity::kScrollByPage;
   EXPECT_EQ(expected_disposition_,
             input_handler_->RouteToTypeSpecificHandler(gesture_));
 
@@ -765,7 +766,7 @@
 
   gesture_.SetType(WebInputEvent::kGestureScrollBegin);
   gesture_.data.scroll_begin.delta_hint_units =
-      WebGestureEvent::ScrollUnits::kPixels;
+      blink::WebScrollGranularity::kScrollByPixel;
   EXPECT_CALL(mock_input_handler_, ScrollAnimatedBegin(_))
       .WillOnce(testing::Return(kImplThreadScrollState));
   EXPECT_EQ(expected_disposition_,
@@ -773,7 +774,7 @@
 
   gesture_.SetType(WebInputEvent::kGestureScrollUpdate);
   gesture_.data.scroll_update.delta_units =
-      WebGestureEvent::ScrollUnits::kPixels;
+      blink::WebScrollGranularity::kScrollByPixel;
 
   EXPECT_CALL(mock_input_handler_, ScrollAnimated(_, _, _))
       .WillOnce(testing::Return(kImplThreadScrollState));
diff --git a/ui/events/blink/input_scroll_elasticity_controller.cc b/ui/events/blink/input_scroll_elasticity_controller.cc
index 7618481..4a6c928 100644
--- a/ui/events/blink/input_scroll_elasticity_controller.cc
+++ b/ui/events/blink/input_scroll_elasticity_controller.cc
@@ -166,10 +166,11 @@
 
       bool enter_momentum = gesture_event.data.scroll_begin.inertial_phase ==
                             blink::WebGestureEvent::kMomentumPhase;
-      bool leave_momentum = gesture_event.data.scroll_begin.inertial_phase ==
-                                blink::WebGestureEvent::kNonMomentumPhase &&
-                            gesture_event.data.scroll_begin.delta_hint_units ==
-                                blink::WebGestureEvent::kPrecisePixels;
+      bool leave_momentum =
+          gesture_event.data.scroll_begin.inertial_phase ==
+              blink::WebGestureEvent::kNonMomentumPhase &&
+          gesture_event.data.scroll_begin.delta_hint_units ==
+              blink::WebScrollGranularity::kScrollByPrecisePixel;
       ObserveRealScrollBegin(enter_momentum, leave_momentum);
       break;
     }
diff --git a/ui/events/gesture_event_details.h b/ui/events/gesture_event_details.h
index 2851738..16ac4dc 100644
--- a/ui/events/gesture_event_details.h
+++ b/ui/events/gesture_event_details.h
@@ -17,18 +17,21 @@
 
 struct EVENTS_BASE_EXPORT GestureEventDetails {
  public:
-  // The definition of ScrollUnits below is consistent with that in
-  // //src/third_party/WebKit/public/platform/WebGestureEvent.h
+  // The definition of ScrollUnits below is consistent with the
+  // WebScrollGranularity enum in
+  // //src/third_party/WebKit/public/platform/web_scroll_types.h
   enum class ScrollUnits {
     PRECISE_PIXELS = 0,  // generated by high precision devices.
     PIXELS,              // large pixel jump duration; should animate to delta.
-    PAGE                 // page (visible viewport) based scrolling.
+    PAGE,                // page (visible viewport) based scrolling.
+    LINE,                // line (visible viewport) based scrolling.
+    DOCUMENT,            // document based scrolling.
   };
 
   GestureEventDetails();
   explicit GestureEventDetails(EventType type);
 
-  // ScrollUnits::PRECISE_PIXELS is the default in WebGestureEvent.h too.
+  // kScrollByPrecisePixels is the default in web_scroll_types.h too.
   GestureEventDetails(EventType type,
                       float delta_x,
                       float delta_y,
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index 708e4f71..852fe1f8 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -666,6 +666,7 @@
       "color_space_unittest.cc",
       "color_transform_unittest.cc",
       "color_utils_unittest.cc",
+      "font_fallback_android_unittest.cc",
       "font_fallback_linux_unittest.cc",
       "font_fallback_mac_unittest.cc",
       "font_list_unittest.cc",
diff --git a/ui/gfx/font_fallback.h b/ui/gfx/font_fallback.h
index 2e5d50a..61ecd268 100644
--- a/ui/gfx/font_fallback.h
+++ b/ui/gfx/font_fallback.h
@@ -19,7 +19,7 @@
 // Given a font, returns the fonts that are suitable for fallback.
 GFX_EXPORT std::vector<Font> GetFallbackFonts(const Font& font);
 
-#if defined(OS_MACOSX) || defined(OS_WIN)
+#if defined(ANDROID) || defined(OS_MACOSX) || defined(OS_WIN)
 
 // Finds a fallback font to render the specified |text| with respect to an
 // initial |font|. Returns the resulting font via out param |result|. Returns
@@ -29,7 +29,7 @@
                                 int text_length,
                                 Font* result);
 
-#endif
+#endif  // ANDROID || OS_MACOSX || OS_WIN
 
 }  // namespace gfx
 
diff --git a/ui/gfx/font_fallback_android.cc b/ui/gfx/font_fallback_android.cc
index e41fa9d..c501746 100644
--- a/ui/gfx/font_fallback_android.cc
+++ b/ui/gfx/font_fallback_android.cc
@@ -7,10 +7,89 @@
 #include <string>
 #include <vector>
 
+#include "base/android/locale_utils.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/common/unicode/uscript.h"
+#include "third_party/icu/source/common/unicode/utf16.h"
+#include "third_party/skia/include/core/SkFontMgr.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "ui/gfx/font.h"
+
 namespace gfx {
 
 std::vector<Font> GetFallbackFonts(const Font& font) {
   return std::vector<Font>();
 }
 
+bool GetFallbackFont(const Font& font,
+                     const base::char16* text,
+                     int text_length,
+                     Font* result) {
+  TRACE_EVENT0("fonts", "gfx::GetFallbackFont");
+
+  if (text_length <= 0)
+    return false;
+
+  sk_sp<SkFontMgr> font_mgr(SkFontMgr::RefDefault());
+
+  std::string locale = base::android::GetDefaultLocaleString();
+  const char* bcp47_locales[] = {locale.c_str()};
+
+  const int font_weight = (font.GetWeight() == Font::Weight::INVALID)
+                              ? static_cast<int>(Font::Weight::NORMAL)
+                              : static_cast<int>(font.GetWeight());
+  const bool italic = (font.GetStyle() & Font::ITALIC) != 0;
+  SkFontStyle skia_style(
+      font_weight, SkFontStyle::kNormal_Width,
+      italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant);
+
+  std::set<SkFontID> tested_typeface;
+  SkString skia_family_name;
+  size_t fewest_missing_glyphs = text_length + 1;
+
+  int offset = 0;
+  while (offset < text_length) {
+    UChar32 code_point;
+    U16_NEXT(text, offset, text_length, code_point);
+
+    sk_sp<SkTypeface> typeface(font_mgr->matchFamilyStyleCharacter(
+        font.GetFontName().c_str(), skia_style,
+        locale.empty() ? nullptr : bcp47_locales, locale.empty() ? 0 : 1,
+        text[offset]));
+    // If the typeface is not found or was already tested, skip it.
+    if (!typeface || !tested_typeface.insert(typeface->uniqueID()).second)
+      continue;
+
+    // Validate that every character has a known glyph in the font.
+    size_t missing_glyphs = 0;
+    int i = 0;
+    while (i < text_length) {
+      UChar32 c;
+      U16_NEXT(text, i, text_length, c);
+      if (typeface->unicharToGlyph(c) == 0)
+        ++missing_glyphs;
+    }
+
+    if (missing_glyphs < fewest_missing_glyphs) {
+      fewest_missing_glyphs = missing_glyphs;
+      typeface->getFamilyName(&skia_family_name);
+    }
+
+    // The font is a valid fallback font for the given text.
+    if (missing_glyphs == 0)
+      break;
+  }
+
+  if (!skia_family_name.isEmpty()) {
+    *result =
+        Font(std::string(skia_family_name.c_str(), skia_family_name.size()),
+             font.GetFontSize());
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace gfx
diff --git a/ui/gfx/font_fallback_android_unittest.cc b/ui/gfx/font_fallback_android_unittest.cc
new file mode 100644
index 0000000..7b09d9e
--- /dev/null
+++ b/ui/gfx/font_fallback_android_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/font_fallback.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+namespace {
+
+static const wchar_t* kFallbackFontTests[] = {
+    L"\u0540\u0541",  // Armenian,
+    L"\u0631\u0632",  // Arabic
+    L"\u0D21\u0D22",  // Malayalam
+    L"\u5203\u5204",  // CJK Unified Ideograph
+};
+
+}  // namespace
+
+TEST(FontFallbackAndroidTest, EmptyStringFallback) {
+  Font base_font;
+  Font fallback_font;
+  bool result = GetFallbackFont(base_font, base::ASCIIToUTF16("").c_str(), 0,
+                                &fallback_font);
+  EXPECT_FALSE(result);
+}
+
+TEST(FontFallbackAndroidTest, FontFallback) {
+  for (const auto* test : kFallbackFontTests) {
+    Font base_font;
+    Font fallback_font;
+    base::string16 text = base::WideToUTF16(test);
+
+    if (!GetFallbackFont(base_font, text.c_str(), text.size(),
+                         &fallback_font)) {
+      ADD_FAILURE() << "Font fallback failed: '" << text << "'";
+    }
+  }
+}
+
+}  // namespace gfx
diff --git a/ui/gfx/icc_profile.cc b/ui/gfx/icc_profile.cc
index 5a9c515..34303ca 100644
--- a/ui/gfx/icc_profile.cc
+++ b/ui/gfx/icc_profile.cc
@@ -163,6 +163,13 @@
                                   internals_->transfer_fn_);
 }
 
+ColorSpace ICCProfile::GetPrimariesOnlyColorSpace() const {
+  ColorSpace result = GetColorSpace();
+  if (result.IsValid())
+    result.transfer_ = ColorSpace::TransferID::IEC61966_2_1;
+  return result;
+}
+
 bool ICCProfile::IsColorSpaceAccurate() const {
   if (!internals_)
     return false;
diff --git a/ui/gfx/icc_profile.h b/ui/gfx/icc_profile.h
index a3a4b96..fa70214 100644
--- a/ui/gfx/icc_profile.h
+++ b/ui/gfx/icc_profile.h
@@ -48,6 +48,10 @@
   // Return a ColorSpace that best represents this ICCProfile.
   ColorSpace GetColorSpace() const;
 
+  // Return a ColorSpace with the primaries from this ICCProfile and an
+  // sRGB transfer function.
+  ColorSpace GetPrimariesOnlyColorSpace() const;
+
   // Returns true if GetColorSpace returns an accurate representation of this
   // ICCProfile. This could be false if the result of GetColorSpace had to
   // approximate transfer functions.
diff --git a/ui/gfx/platform_font_skia_unittest.cc b/ui/gfx/platform_font_skia_unittest.cc
index d2c6b99..acdeab9c 100644
--- a/ui/gfx/platform_font_skia_unittest.cc
+++ b/ui/gfx/platform_font_skia_unittest.cc
@@ -60,12 +60,17 @@
 
 class PlatformFontSkiaTest : public testing::Test {
  public:
-  PlatformFontSkiaTest() {
+  PlatformFontSkiaTest() = default;
+  ~PlatformFontSkiaTest() override = default;
+
+  void SetUp() override {
     original_font_delegate_ = SkiaFontDelegate::instance();
     SkiaFontDelegate::SetInstance(&test_font_delegate_);
+    PlatformFontSkia::ReloadDefaultFont();
   }
 
-  ~PlatformFontSkiaTest() override {
+  void TearDown() override {
+    DCHECK_EQ(&test_font_delegate_, SkiaFontDelegate::instance());
     SkiaFontDelegate::SetInstance(
         const_cast<SkiaFontDelegate*>(original_font_delegate_));
     PlatformFontSkia::ReloadDefaultFont();
diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc
index b81cc87..a7a523a 100644
--- a/ui/gfx/render_text_harfbuzz.cc
+++ b/ui/gfx/render_text_harfbuzz.cc
@@ -1799,7 +1799,7 @@
 
   std::string preferred_fallback_family;
 
-#if defined(OS_WIN) || defined(OS_MACOSX)
+#if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MACOSX)
   Font fallback_font(primary_font);
   bool fallback_found;
   {
@@ -1820,7 +1820,7 @@
     if (runs.empty())
       return;
   }
-#endif
+#endif  // OS_ANDROID || OS_WIN || OS_MACOSX
 
   std::vector<Font> fallback_font_list;
   {
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index af712e1..be0271f 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -4246,6 +4246,53 @@
 }
 #endif  // !defined(OS_LINUX) && !defined(OS_ANDROID)
 
+// Ensure that the fallback fonts offered by GetFallbackFont() support glyphs
+// for different languages.
+TEST_F(RenderTextTest, HarfBuzz_FallbackFontsSupportGlyphs) {
+  // The word 'test' in different languages.
+  static const wchar_t* kLanguageTests[] = {
+      L"test", L"اختبار", L"Δοκιμή", L"परीक्षा", L"تست", L"Փորձարկում",
+  };
+
+  for (const wchar_t* text : kLanguageTests) {
+    RenderTextHarfBuzz* render_text = GetRenderText();
+    render_text->SetText(WideToUTF16(text));
+    test_api()->EnsureLayout();
+
+    const internal::TextRunList* run_list = GetHarfBuzzRunList();
+    ASSERT_EQ(1U, run_list->size());
+    int missing_glyphs = run_list->runs()[0]->CountMissingGlyphs();
+    if (missing_glyphs != 0) {
+      ADD_FAILURE() << "Text '" << text << "' is missing " << missing_glyphs
+                    << " glyphs.";
+    }
+  }
+}
+
+TEST_F(RenderTextTest, HarfBuzz_MultiRunsSupportGlyphs) {
+  static const wchar_t* kLanguageTests[] = {
+      L"www.اختبار.com",
+      L"(اختبار)",
+      L"/ זה (מבחן) /",
+  };
+
+  for (const wchar_t* text : kLanguageTests) {
+    RenderTextHarfBuzz* render_text = GetRenderText();
+    render_text->SetText(WideToUTF16(text));
+    test_api()->EnsureLayout();
+
+    int missing_glyphs = 0;
+    const internal::TextRunList* run_list = GetHarfBuzzRunList();
+    for (const auto& run : run_list->runs())
+      missing_glyphs += run->CountMissingGlyphs();
+
+    if (missing_glyphs != 0) {
+      ADD_FAILURE() << "Text '" << text << "' is missing " << missing_glyphs
+                    << " glyphs.";
+    }
+  }
+}
+
 // Ensure that the width reported by RenderText is sufficient for drawing. Draws
 // to a canvas and checks if any pixel beyond the bounding rectangle is colored.
 TEST_F(RenderTextTest, TextDoesntClip) {
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 9ccfe71..2f28b7c 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -769,9 +769,9 @@
     ]
     deps += [
       "//components/crash/core/common",
+      "//components/remote_cocoa/app_shim",
       "//ui/accelerated_widget_mac",
       "//ui/events:dom_keycode_converter",
-      "//ui/views_bridge_mac",
     ]
     public_deps += [ "//ui/views_bridge_mac:mojo" ]
     libs = [
@@ -944,7 +944,7 @@
     ]
   }
   if (is_mac) {
-    deps += [ "//ui/views_bridge_mac" ]
+    deps += [ "//components/remote_cocoa/app_shim" ]
   }
 
   if (use_aura) {
@@ -1187,8 +1187,8 @@
     sources -= [ "controls/native/native_view_host_unittest.cc" ]
 
     public_deps = [
+      "//components/remote_cocoa/app_shim",
       "//ui/accelerated_widget_mac",
-      "//ui/views_bridge_mac:views_bridge_mac",
     ]
   }
 
@@ -1291,7 +1291,7 @@
       "cocoa/bridged_native_widget_interactive_uitest.mm",
       "widget/native_widget_mac_interactive_uitest.mm",
     ]
-    deps += [ "//ui/views_bridge_mac" ]
+    deps += [ "//components/remote_cocoa/app_shim" ]
   }
 
   if (use_aura) {
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 28691c68..08e0d409 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -277,12 +277,15 @@
 }
 
 void Combobox::SetSelectedIndex(int index) {
+  if (selected_index_ == index)
+    return;
+
   selected_index_ = index;
   if (size_to_largest_label_) {
-    SchedulePaint();
+    OnPropertyChanged(&selected_index_, kPropertyEffectsPaint);
   } else {
     content_size_ = GetContentSize();
-    PreferredSizeChanged();
+    OnPropertyChanged(&selected_index_, kPropertyEffectsPreferredSizeChanged);
   }
 }
 
@@ -654,4 +657,8 @@
   return selector_.get();
 }
 
+BEGIN_METADATA(Combobox)
+ADD_PROPERTY_METADATA(Combobox, int, SelectedIndex)
+END_METADATA()
+
 }  // namespace views
diff --git a/ui/views/controls/combobox/combobox.h b/ui/views/controls/combobox/combobox.h
index e26f348..3bb2252e 100644
--- a/ui/views/controls/combobox/combobox.h
+++ b/ui/views/controls/combobox/combobox.h
@@ -37,6 +37,8 @@
                               public PrefixDelegate,
                               public ButtonListener {
  public:
+  METADATA_HEADER(Combobox);
+
   // The combobox's class name.
   static const char kViewClassName[];
   static constexpr int kDefaultComboboxTextContext = style::CONTEXT_BUTTON;
@@ -61,7 +63,7 @@
   void ModelChanged();
 
   // Gets/Sets the selected index.
-  int selected_index() const { return selected_index_; }
+  int GetSelectedIndex() const { return selected_index_; }
   void SetSelectedIndex(int index);
 
   // Looks for the first occurrence of |value| in |model()|. If found, selects
diff --git a/ui/views/controls/combobox/combobox_unittest.cc b/ui/views/controls/combobox/combobox_unittest.cc
index 4d1999a..c301f313 100644
--- a/ui/views/controls/combobox/combobox_unittest.cc
+++ b/ui/views/controls/combobox/combobox_unittest.cc
@@ -164,7 +164,7 @@
   ~TestComboboxListener() override = default;
 
   void OnPerformAction(views::Combobox* combobox) override {
-    perform_action_index_ = combobox->selected_index();
+    perform_action_index_ = combobox->GetSelectedIndex();
     actions_performed_++;
   }
 
@@ -288,43 +288,43 @@
 TEST_F(ComboboxTest, KeyTestMac) {
   InitCombobox(nullptr);
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(1, menu_show_count_);
 
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(2, menu_show_count_);
 
   PressKey(ui::VKEY_UP, ui::EF_COMMAND_DOWN);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(3, menu_show_count_);
 
   PressKey(ui::VKEY_DOWN, ui::EF_COMMAND_DOWN);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(4, menu_show_count_);
 
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(5, menu_show_count_);
 
   PressKey(ui::VKEY_RIGHT);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(5, menu_show_count_);
 
   PressKey(ui::VKEY_LEFT);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(5, menu_show_count_);
 
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(6, menu_show_count_);
 
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(6, menu_show_count_);
 
   PressKey(ui::VKEY_NEXT);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_EQ(6, menu_show_count_);
 }
 #endif
@@ -358,22 +358,22 @@
 TEST_F(ComboboxTest, KeyTest) {
   InitCombobox(nullptr);
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(model_->GetItemCount(), combobox_->selected_index() + 1);
+  EXPECT_EQ(model_->GetItemCount(), combobox_->GetSelectedIndex() + 1);
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(2, combobox_->selected_index());
+  EXPECT_EQ(2, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_RIGHT);
-  EXPECT_EQ(2, combobox_->selected_index());
+  EXPECT_EQ(2, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_LEFT);
-  EXPECT_EQ(2, combobox_->selected_index());
+  EXPECT_EQ(2, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_NEXT);
-  EXPECT_EQ(model_->GetItemCount() - 1, combobox_->selected_index());
+  EXPECT_EQ(model_->GetItemCount(), combobox_->GetSelectedIndex() + 1);
 }
 
 // Verifies that we don't select a separator line in combobox when navigating
@@ -382,19 +382,19 @@
   std::set<int> separators;
   separators.insert(2);
   InitCombobox(&separators);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(9, combobox_->selected_index());
+  EXPECT_EQ(9, combobox_->GetSelectedIndex());
 }
 
 // Verifies that we never select the separator that is in the beginning of the
@@ -403,19 +403,19 @@
   std::set<int> separators;
   separators.insert(0);
   InitCombobox(&separators);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(2, combobox_->selected_index());
+  EXPECT_EQ(2, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(2, combobox_->selected_index());
+  EXPECT_EQ(2, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(9, combobox_->selected_index());
+  EXPECT_EQ(9, combobox_->GetSelectedIndex());
 }
 
 // Verifies that we never select the separator that is in the end of the
@@ -426,11 +426,11 @@
   InitCombobox(&separators);
   combobox_->SetSelectedIndex(8);
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(8, combobox_->selected_index());
+  EXPECT_EQ(8, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(7, combobox_->selected_index());
+  EXPECT_EQ(7, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(8, combobox_->selected_index());
+  EXPECT_EQ(8, combobox_->GetSelectedIndex());
 }
 
 // Verifies that we never select any of the adjacent separators (multiple
@@ -442,19 +442,19 @@
   separators.insert(1);
   separators.insert(2);
   InitCombobox(&separators);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(4, combobox_->selected_index());
+  EXPECT_EQ(4, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_NEXT);
-  EXPECT_EQ(9, combobox_->selected_index());
+  EXPECT_EQ(9, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(9, combobox_->selected_index());
+  EXPECT_EQ(9, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
 }
 
 // Verifies that we never select any of the adjacent separators (multiple
@@ -468,9 +468,9 @@
   InitCombobox(&separators);
   combobox_->SetSelectedIndex(3);
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(7, combobox_->selected_index());
+  EXPECT_EQ(7, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(3, combobox_->selected_index());
+  EXPECT_EQ(3, combobox_->GetSelectedIndex());
 }
 
 // Verifies that we never select any of the adjacent separators (multiple
@@ -484,17 +484,17 @@
   InitCombobox(&separators);
   combobox_->SetSelectedIndex(6);
   PressKey(ui::VKEY_DOWN);
-  EXPECT_EQ(6, combobox_->selected_index());
+  EXPECT_EQ(6, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_UP);
-  EXPECT_EQ(5, combobox_->selected_index());
+  EXPECT_EQ(5, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_HOME);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_NEXT);
-  EXPECT_EQ(6, combobox_->selected_index());
+  EXPECT_EQ(6, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_PRIOR);
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   PressKey(ui::VKEY_END);
-  EXPECT_EQ(6, combobox_->selected_index());
+  EXPECT_EQ(6, combobox_->GetSelectedIndex());
 }
 #endif  // !OS_MACOSX
 
@@ -517,13 +517,13 @@
 // Verifies selecting the first matching value (and returning whether found).
 TEST_F(ComboboxTest, SelectValue) {
   InitCombobox(nullptr);
-  ASSERT_EQ(model_->GetDefaultIndex(), combobox_->selected_index());
+  ASSERT_EQ(model_->GetDefaultIndex(), combobox_->GetSelectedIndex());
   EXPECT_TRUE(combobox_->SelectValue(ASCIIToUTF16("PEANUT BUTTER")));
-  EXPECT_EQ(0, combobox_->selected_index());
+  EXPECT_EQ(0, combobox_->GetSelectedIndex());
   EXPECT_TRUE(combobox_->SelectValue(ASCIIToUTF16("JELLY")));
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
   EXPECT_FALSE(combobox_->SelectValue(ASCIIToUTF16("BANANAS")));
-  EXPECT_EQ(1, combobox_->selected_index());
+  EXPECT_EQ(1, combobox_->GetSelectedIndex());
 }
 
 TEST_F(ComboboxTest, ListenerHandlesDelete) {
diff --git a/ui/views/examples/box_layout_example.cc b/ui/views/examples/box_layout_example.cc
index 1fa7c23..40cc17c 100644
--- a/ui/views/examples/box_layout_example.cc
+++ b/ui/views/examples/box_layout_example.cc
@@ -72,11 +72,11 @@
     UpdateLayoutManager();
   } else if (combobox == main_axis_alignment_) {
     layout_->set_main_axis_alignment(static_cast<BoxLayout::MainAxisAlignment>(
-        main_axis_alignment_->selected_index()));
+        main_axis_alignment_->GetSelectedIndex()));
   } else if (combobox == cross_axis_alignment_) {
     layout_->set_cross_axis_alignment(
         static_cast<BoxLayout::CrossAxisAlignment>(
-            cross_axis_alignment_->selected_index()));
+            cross_axis_alignment_->GetSelectedIndex()));
   }
   RefreshLayoutPanel(false);
 }
@@ -112,13 +112,14 @@
   base::StringToInt(default_flex_->text(), &default_flex);
   base::StringToInt(min_cross_axis_size_->text(), &min_cross_size);
   auto layout = std::make_unique<BoxLayout>(
-      orientation_->selected_index() == 0 ? BoxLayout::Orientation::kHorizontal
-                                          : BoxLayout::Orientation::kVertical,
+      orientation_->GetSelectedIndex() == 0
+          ? BoxLayout::Orientation::kHorizontal
+          : BoxLayout::Orientation::kVertical,
       gfx::Insets(0, 0), child_spacing, collapse_margins_->checked());
   layout->set_cross_axis_alignment(static_cast<BoxLayout::CrossAxisAlignment>(
-      cross_axis_alignment_->selected_index()));
+      cross_axis_alignment_->GetSelectedIndex()));
   layout->set_main_axis_alignment(static_cast<BoxLayout::MainAxisAlignment>(
-      main_axis_alignment_->selected_index()));
+      main_axis_alignment_->GetSelectedIndex()));
   layout->SetDefaultFlex(default_flex);
   layout->set_minimum_cross_axis_size(min_cross_size);
   View* const panel = layout_panel();
diff --git a/ui/views/examples/combobox_example.cc b/ui/views/examples/combobox_example.cc
index bf154a5..d8acc7b 100644
--- a/ui/views/examples/combobox_example.cc
+++ b/ui/views/examples/combobox_example.cc
@@ -59,9 +59,10 @@
 
 void ComboboxExample::OnPerformAction(Combobox* combobox) {
   DCHECK_EQ(combobox, combobox_);
-  PrintStatus("Selected: %s", base::UTF16ToUTF8(combobox->model()->GetItemAt(
-                                                    combobox->selected_index()))
-                                  .c_str());
+  PrintStatus("Selected: %s",
+              base::UTF16ToUTF8(
+                  combobox->model()->GetItemAt(combobox->GetSelectedIndex()))
+                  .c_str());
 }
 
 }  // namespace examples
diff --git a/ui/views/examples/dialog_example.cc b/ui/views/examples/dialog_example.cc
index 7582d1d9..70d517c 100644
--- a/ui/views/examples/dialog_example.cc
+++ b/ui/views/examples/dialog_example.cc
@@ -215,10 +215,10 @@
 ui::ModalType DialogExample::GetModalType() const {
   // "Fake" modeless happens when a DialogDelegate specifies window-modal, but
   // doesn't provide a parent window.
-  if (mode_->selected_index() == kFakeModeless)
+  if (mode_->GetSelectedIndex() == kFakeModeless)
     return ui::MODAL_TYPE_WINDOW;
 
-  return static_cast<ui::ModalType>(mode_->selected_index());
+  return static_cast<ui::ModalType>(mode_->GetSelectedIndex());
 }
 
 int DialogExample::GetDialogButtons() const {
@@ -266,7 +266,7 @@
       // constrained_window::CreateBrowserModalDialogViews() allows dialogs to
       // be created as MODAL_TYPE_WINDOW without specifying a parent.
       gfx::NativeView parent = nullptr;
-      if (mode_->selected_index() != kFakeModeless)
+      if (mode_->GetSelectedIndex() != kFakeModeless)
         parent = container()->GetWidget()->GetNativeView();
 
       DialogDelegate::CreateDialogWidget(
diff --git a/ui/views/examples/examples_window.cc b/ui/views/examples/examples_window.cc
index e70e1f4c..ecd2878fa 100644
--- a/ui/views/examples/examples_window.cc
+++ b/ui/views/examples/examples_window.cc
@@ -202,10 +202,10 @@
   // ComboboxListener:
   void OnPerformAction(Combobox* combobox) override {
     DCHECK_EQ(combobox, combobox_);
-    DCHECK(combobox->selected_index() < combobox_model_->GetItemCount());
+    DCHECK(combobox->GetSelectedIndex());
     example_shown_->RemoveAllChildViews(false);
     example_shown_->AddChildView(
-        combobox_model_->GetItemViewAt(combobox->selected_index()));
+        combobox_model_->GetItemViewAt(combobox->GetSelectedIndex()));
     example_shown_->RequestFocus();
     SetStatus(std::string());
     InvalidateLayout();
diff --git a/ui/views/examples/flex_layout_example.cc b/ui/views/examples/flex_layout_example.cc
index 353bf30..f8697b93 100644
--- a/ui/views/examples/flex_layout_example.cc
+++ b/ui/views/examples/flex_layout_example.cc
@@ -66,13 +66,13 @@
       LayoutAlignment::kCenter, LayoutAlignment::kEnd};
 
   if (combobox == orientation_) {
-    layout_->SetOrientation(orientations[combobox->selected_index()]);
+    layout_->SetOrientation(orientations[combobox->GetSelectedIndex()]);
   } else if (combobox == main_axis_alignment_) {
     layout_->SetMainAxisAlignment(
-        main_axis_alignments[combobox->selected_index()]);
+        main_axis_alignments[combobox->GetSelectedIndex()]);
   } else if (combobox == cross_axis_alignment_) {
     layout_->SetCrossAxisAlignment(
-        cross_axis_alignments[combobox->selected_index()]);
+        cross_axis_alignments[combobox->GetSelectedIndex()]);
   }
   RefreshLayoutPanel(false);
 }
diff --git a/ui/views/examples/label_example.cc b/ui/views/examples/label_example.cc
index 0f80eb2..f57bbc8e 100644
--- a/ui/views/examples/label_example.cc
+++ b/ui/views/examples/label_example.cc
@@ -140,10 +140,10 @@
 void LabelExample::OnPerformAction(Combobox* combobox) {
   if (combobox == alignment_) {
     custom_label_->SetHorizontalAlignment(
-        static_cast<gfx::HorizontalAlignment>(combobox->selected_index()));
+        static_cast<gfx::HorizontalAlignment>(combobox->GetSelectedIndex()));
   } else if (combobox == elide_behavior_) {
     custom_label_->SetElideBehavior(
-        static_cast<gfx::ElideBehavior>(combobox->selected_index()));
+        static_cast<gfx::ElideBehavior>(combobox->GetSelectedIndex()));
   }
 }
 
diff --git a/ui/views/examples/text_example.cc b/ui/views/examples/text_example.cc
index f1cb5f5..f9fad26 100644
--- a/ui/views/examples/text_example.cc
+++ b/ui/views/examples/text_example.cc
@@ -213,7 +213,7 @@
     flags &= ~(gfx::Canvas::TEXT_ALIGN_LEFT |
                gfx::Canvas::TEXT_ALIGN_CENTER |
                gfx::Canvas::TEXT_ALIGN_RIGHT);
-    switch (combobox->selected_index()) {
+    switch (combobox->GetSelectedIndex()) {
       case 0:
         break;
       case 1:
@@ -227,7 +227,7 @@
         break;
     }
   } else if (combobox == text_cb_) {
-    switch (combobox->selected_index()) {
+    switch (combobox->GetSelectedIndex()) {
       case 0:
         text_view_->set_text(base::ASCIIToUTF16(kShortText));
         break;
@@ -242,7 +242,7 @@
         break;
     }
   } else if (combobox == eliding_cb_) {
-    switch (combobox->selected_index()) {
+    switch (combobox->GetSelectedIndex()) {
       case 0:
         text_view_->set_elide(gfx::ELIDE_TAIL);
         break;
@@ -255,7 +255,7 @@
     }
   } else if (combobox == prefix_cb_) {
     flags &= ~(gfx::Canvas::SHOW_PREFIX | gfx::Canvas::HIDE_PREFIX);
-    switch (combobox->selected_index()) {
+    switch (combobox->GetSelectedIndex()) {
       case 0:
         break;
       case 1:
@@ -266,7 +266,7 @@
         break;
     }
   } else if (combobox == weight_cb_) {
-    text_view_->SetWeight(kFontWeights[combobox->selected_index()]);
+    text_view_->SetWeight(kFontWeights[combobox->GetSelectedIndex()]);
   }
   text_view_->set_flags(flags);
   text_view_->SchedulePaint();
diff --git a/ui/views/mus/mus_client.cc b/ui/views/mus/mus_client.cc
index 41920e9d..84cb86e 100644
--- a/ui/views/mus/mus_client.cc
+++ b/ui/views/mus/mus_client.cc
@@ -134,7 +134,6 @@
   // we are still valid.
   owned_window_tree_client_.reset();
   window_tree_client_ = nullptr;
-  ui::OSExchangeDataProviderFactory::SetFactory(nullptr);
   ui::Clipboard::DestroyClipboardForCurrentThread();
 
   if (ViewsDelegate::GetInstance()) {
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 97c4075..142d4ef01 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -1849,6 +1849,8 @@
 }
 
 void View::HandlePropertyChangeEffects(PropertyEffects effects) {
+  if (effects & kPropertyEffectsPreferredSizeChanged)
+    PreferredSizeChanged();
   if (effects & kPropertyEffectsLayout)
     InvalidateLayout();
   if (effects & kPropertyEffectsPaint)
diff --git a/ui/views/view.h b/ui/views/view.h
index 205a8a3..92082cb 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -142,6 +142,9 @@
   // Changes to the property should cause the container to schedule a painting
   // update.
   kPropertyEffectsPaint = 0x00000002,
+  // Changes to the property should cause the preferred size to change. This
+  // implies kPropertyEffectsLayout.
+  kPropertyEffectsPreferredSizeChanged = 0x00000004,
 };
 
 /////////////////////////////////////////////////////////////////////////////
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index 4a593c73..340fe7a8 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -1422,6 +1422,9 @@
   ASSERT_EQ(2u, children.size());
   EXPECT_TRUE(children.count(sheet_widget));
 
+  // Sheets are not child windows of their parent NSWindow, though.
+  ASSERT_EQ(0u, [native_parent childWindows].count);
+
   // Modal, so the close button in the parent window should get disabled.
   EXPECT_FALSE([parent_close_button isEnabled]);
 
@@ -1443,6 +1446,7 @@
   widget_observer.WaitForVisibleCounts(1, 1);
   EXPECT_FALSE(sheet_widget->IsVisible());
   [native_parent makeKeyAndOrderFront:nil];
+  ASSERT_EQ(0u, [native_parent childWindows].count);
   widget_observer.WaitForVisibleCounts(2, 1);
   EXPECT_TRUE(sheet_widget->IsVisible());
 
diff --git a/ui/views_bridge_mac/BUILD.gn b/ui/views_bridge_mac/BUILD.gn
index 6e19434..e38f548f 100644
--- a/ui/views_bridge_mac/BUILD.gn
+++ b/ui/views_bridge_mac/BUILD.gn
@@ -4,66 +4,6 @@
 
 import("//mojo/public/tools/bindings/mojom.gni")
 
-config("views_bridge_mac_warnings") {
-  if (is_mac) {
-    # TODO(thakis): Remove this once http://crbug.com/383820 is figured out
-    cflags = [ "-Wno-nonnull" ]
-  }
-}
-
-component("views_bridge_mac") {
-  assert(is_mac)
-
-  configs += [ ":views_bridge_mac_warnings" ]
-  sources = [
-    "alert.h",
-    "alert.mm",
-    "bridge_factory_impl.h",
-    "bridge_factory_impl.mm",
-    "bridged_content_view.h",
-    "bridged_content_view.mm",
-    "bridged_content_view_touch_bar.mm",
-    "bridged_native_widget_host_helper.h",
-    "bridged_native_widget_impl.h",
-    "bridged_native_widget_impl.mm",
-    "browser_native_widget_window_mac.h",
-    "browser_native_widget_window_mac.mm",
-    "cocoa_mouse_capture.h",
-    "cocoa_mouse_capture.mm",
-    "cocoa_mouse_capture_delegate.h",
-    "cocoa_window_move_loop.h",
-    "cocoa_window_move_loop.mm",
-    "drag_drop_client.h",
-    "native_widget_mac_frameless_nswindow.h",
-    "native_widget_mac_frameless_nswindow.mm",
-    "native_widget_mac_nswindow.h",
-    "native_widget_mac_nswindow.mm",
-    "views_bridge_mac_export.h",
-    "views_nswindow_delegate.h",
-    "views_nswindow_delegate.mm",
-    "views_scrollbar_bridge.h",
-    "views_scrollbar_bridge.mm",
-    "window_touch_bar_delegate.h",
-  ]
-  defines = [ "VIEWS_BRIDGE_MAC_IMPLEMENTATION" ]
-  deps = [
-    ":mojo",
-    "//base",
-    "//base:i18n",
-    "//components/crash/core/common",
-    "//mojo/public/cpp/bindings",
-    "//ui/accelerated_widget_mac",
-    "//ui/base",
-    "//ui/base/ime:ime",
-    "//ui/events",
-    "//ui/gfx",
-  ]
-  libs = [
-    "Cocoa.framework",
-    "QuartzCore.framework",
-  ]
-}
-
 mojom("mojo") {
   assert(is_mac)
 
diff --git a/ui/views_bridge_mac/bridged_content_view.mm b/ui/views_bridge_mac/bridged_content_view.mm
index 1fa63c0..874edb04f 100644
--- a/ui/views_bridge_mac/bridged_content_view.mm
+++ b/ui/views_bridge_mac/bridged_content_view.mm
@@ -11,7 +11,6 @@
 #import "base/mac/sdk_forward_declarations.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/sys_string_conversions.h"
-#include "skia/ext/skia_utils_mac.h"
 #import "ui/base/cocoa/appkit_utils.h"
 #include "ui/base/cocoa/cocoa_base_utils.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
diff --git a/ui/views_bridge_mac/bridged_native_widget_impl.mm b/ui/views_bridge_mac/bridged_native_widget_impl.mm
index 7c5face..67bedd45 100644
--- a/ui/views_bridge_mac/bridged_native_widget_impl.mm
+++ b/ui/views_bridge_mac/bridged_native_widget_impl.mm
@@ -1345,18 +1345,26 @@
     // are currently visible. They probably aren't, since the parent was hidden
     // prior to this, but they could have been made visible in other ways.
     if (child->wants_to_be_visible_) {
-      ++visible_bridged_children;
-      // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
-      // screen. However, that by itself is insufficient to guarantee a correct
-      // z-order relationship. If this function is being called from a z-order
-      // change in the parent, orderWindow turns out to be unreliable (i.e. the
-      // ordering doesn't always take effect). What this actually relies on is
-      // the resulting call to OnVisibilityChanged() in the child, which will
-      // then insert itself into -[NSWindow childWindows] to let Cocoa do its
-      // internal layering magic.
-      [child->ns_window() orderWindow:NSWindowAbove
-                           relativeTo:parent_window_number];
-      DCHECK(child->window_visible_);
+      if ([child->ns_window() isSheet]) {
+        // Sheets should not be counted as children since their NSWindows are
+        // not children of this NSWindow, and they should not be directly
+        // ordered back in - that causes them to lose their sheet-ness.
+        child->ShowAsModalSheet();
+      } else {
+        ++visible_bridged_children;
+
+        // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
+        // screen. However, that by itself is insufficient to guarantee a
+        // correct z-order relationship. If this function is being called from a
+        // z-order change in the parent, orderWindow turns out to be unreliable
+        // (i.e. the ordering doesn't always take effect). What this actually
+        // relies on is the resulting call to OnVisibilityChanged() in the
+        // child, which will then insert itself into -[NSWindow childWindows] to
+        // let Cocoa do its internal layering magic.
+        [child->ns_window() orderWindow:NSWindowAbove
+                             relativeTo:parent_window_number];
+        DCHECK(child->window_visible_);
+      }
     }
     CHECK_EQ(child_count, child_windows_.size());
   }
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
index 98f14a7..d26bb57 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/BUILD.gn
@@ -9,11 +9,20 @@
 js_type_check("closure_compile") {
   deps = [
     ":cellular_setup",
+    ":mojo_interface_provider",
   ]
 }
 
 js_library("cellular_setup") {
   deps = [
+    ":mojo_interface_provider",
     "//ui/webui/resources/js:i18n_behavior",
   ]
 }
+
+js_library("mojo_interface_provider") {
+  deps = [
+    "//chromeos/services/cellular_setup/public/mojom:mojom_js_library_for_compile",
+    "//ui/webui/resources/js:cr",
+  ]
+}
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.html
index 11c88bf3..2173204a 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
+<link rel="import" href="chrome://resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html">
 
 <dom-module id="cellular-setup">
   <template>
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
index b4e2344..211e451 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_setup.js
@@ -7,4 +7,16 @@
   is: 'cellular-setup',
 
   behaviors: [I18nBehavior],
+
+  /**
+   * Provides an interface to the CellularSetup Mojo service.
+   * @private {?cellular_setup.MojoInterfaceProvider}
+   */
+  mojoInterfaceProvider_: null,
+
+  /** @override */
+  created: function() {
+    this.mojoInterfaceProvider_ =
+        cellular_setup.MojoInterfaceProviderImpl.getInstance();
+  },
 });
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html
new file mode 100644
index 0000000..115d282b
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.html
@@ -0,0 +1,4 @@
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.html">
+<link rel="import" href="chrome://resources/mojo/chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.html">
+<script src="mojo_interface_provider.js"></script>
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js
new file mode 100644
index 0000000..11cb03ee
--- /dev/null
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.js
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cellular_setup', function() {
+  /** @interface */
+  class MojoInterfaceProvider {
+    /** @return {!chromeos.cellularSetup.mojom.CellularSetupProxy} */
+    getMojoServiceProxy() {}
+  }
+
+  /** @implements {cellular_setup.MojoInterfaceProvider} */
+  class MojoInterfaceProviderImpl {
+    constructor() {
+      /** @private {?chromeos.cellularSetup.mojom.CellularSetupProxy} */
+      this.proxy_ = null;
+    }
+
+    /** @override */
+    getMojoServiceProxy() {
+      if (!this.proxy_) {
+        this.proxy_ = chromeos.cellularSetup.mojom.CellularSetup.getProxy();
+      }
+
+      return this.proxy_;
+    }
+  }
+
+  cr.addSingletonGetter(MojoInterfaceProviderImpl);
+
+  return {
+    MojoInterfaceProvider: MojoInterfaceProvider,
+    MojoInterfaceProviderImpl: MojoInterfaceProviderImpl,
+  };
+});
diff --git a/ui/webui/resources/cr_components/cr_components_resources.grdp b/ui/webui/resources/cr_components/cr_components_resources.grdp
index 6248ce7..f72a13f 100644
--- a/ui/webui/resources/cr_components/cr_components_resources.grdp
+++ b/ui/webui/resources/cr_components/cr_components_resources.grdp
@@ -260,14 +260,22 @@
                compress="gzip" />
 
     <!-- Shared between cellular setup flow and OOBE. -->
-    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_BROWSER_HTML"
+    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_HTML"
                file="cr_components/chromeos/cellular_setup/cellular_setup.html"
                type="chrome_html"
                compress="gzip" />
-    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_BROWSER_JS"
+    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_CELLULAR_SETUP_JS"
                file="cr_components/chromeos/cellular_setup/cellular_setup.js"
                type="chrome_html"
                compress="gzip" />
+    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_MOJO_INTERFACE_PROVIDER_HTML"
+               file="cr_components/chromeos/cellular_setup/mojo_interface_provider.html"
+               type="chrome_html"
+               compress="gzip" />
+    <structure name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_MOJO_INTERFACE_PROVIDER_JS"
+               file="cr_components/chromeos/cellular_setup/mojo_interface_provider.js"
+               type="chrome_html"
+               compress="gzip" />
 
     <!-- Shared between MultiDevice setup flow and OOBE. -->
     <structure name="IDR_WEBUI_CHROMEOS_MULTIDEVICE_SETUP_MULTIDEVICE_SETUP_BROWSER_PROXY_HTML"
diff --git a/ui/webui/resources/js/cr/ui/focus_row_behavior.js b/ui/webui/resources/js/cr/ui/focus_row_behavior.js
index 16ffc368..daf3b0e 100644
--- a/ui/webui/resources/js/cr/ui/focus_row_behavior.js
+++ b/ui/webui/resources/js/cr/ui/focus_row_behavior.js
@@ -288,6 +288,8 @@
       if (!this.lastFocused || restoreFocusToFirst) {
         const firstFocusable = assert(this.firstControl_);
         firstFocusable.focus();
+      } else if (this.getFocusRow().getElements().includes(this.lastFocused)) {
+        this.lastFocused.focus();
       } else if (lastKeyDownKey == 'ArrowDown' || lastKeyDownKey == 'ArrowUp') {
         this.row_.getEquivalentElement(this.lastFocused).focus();
       }