diff --git a/DEPS b/DEPS
index 9a34191..9850e9d 100644
--- a/DEPS
+++ b/DEPS
@@ -116,11 +116,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': 'd7157b2ccf53febde957a2c229e5f59fbd4f5cf2',
+  'skia_revision': '44764000b49cddcec8b6733375078404f97a00fc',
   # 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': 'ebedb7ebb3f46fb54efc4601bbd5580989cb575f',
+  'v8_revision': 'c10b42544e3c97ecb259ede63fa2ccb78c8396dc',
   # 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.
@@ -128,7 +128,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'd1a55e393e65601ba26e2d76bd46a12fc6bc4e3e',
+  'angle_revision': 'ccad5e333472572ecda3c0ec80a702c7dcc5e95f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -140,7 +140,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'a89ef2b38df07e4e19c8f43e8f58e265c190336b',
+  'pdfium_revision': '2585eafdcba3fff4b6357f133cd9a74e8d4e84a5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -176,7 +176,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': '6b8b30c8a1b138f083cb474871e500662397b3f2',
+  'catapult_revision': '4925b069e1cfc5b50ead614e28d3367bfcd72349',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -224,7 +224,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': '5beeee15c175a15e904ad7ce3df37d1d0d55bd06',
+  'spv_tools_revision': 'a6150a3fe778f62ac85b9fc8a2547c69e7607337',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -655,7 +655,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd5fa47311da1d0457f3ba3c12a0480bcb77449af',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '88c15c341d97a21a2fe5511bc9f91d207cd570f4',
       'condition': 'checkout_linux',
   },
 
@@ -946,7 +946,7 @@
 
   # Minizip library. Used on Chrome OS.
   'src/third_party/minizip/src': {
-      'url': Var('chromium_git') + '/external/github.com/nmoinvaz/minizip' + '@' + 'c47090678d687742eddc60d07c5430279af416d8',
+      'url': Var('chromium_git') + '/external/github.com/nmoinvaz/minizip' + '@' + '4d4c9db5b019e71b4a40fb41ab21fb47de12ae69',
       'condition': 'checkout_linux',
   },
 
@@ -1166,7 +1166,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '0d55c887e92b645f6effe753528323ab2ffd94c2',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'fdc635d2a8ffceeec44b57a83c9ca102fba1a88f',
+    Var('webrtc_git') + '/src.git' + '@' + '83aa5ace99a52bbe85055d89ed6837930f8d1f2c',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1197,7 +1197,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8eae8558004a4404a5f6ea5437fdf5b4cc92ac55',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e04de3408e65f698419363302c17410eb78bb42d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwPicture.java b/android_webview/java/src/org/chromium/android_webview/AwPicture.java
index c8c8d3b5..8993543 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwPicture.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwPicture.java
@@ -68,7 +68,6 @@
         nativeDraw(mNativeAwPicture, canvas);
     }
 
-    @Override
     @SuppressWarnings("deprecation")
     public void writeToStream(OutputStream stream) {
         unsupportedOperation();
diff --git a/android_webview/renderer/aw_content_renderer_client.cc b/android_webview/renderer/aw_content_renderer_client.cc
index 7e76648..02354579 100644
--- a/android_webview/renderer/aw_content_renderer_client.cc
+++ b/android_webview/renderer/aw_content_renderer_client.cc
@@ -195,15 +195,12 @@
     content::RenderFrame* render_frame,
     const blink::WebURLRequest& failed_request,
     const blink::WebURLError& error,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   std::string err;
   if (error.reason() == net::ERR_TEMPORARILY_THROTTLED)
     err = kThrottledErrorDescription;
   else
     err = net::ErrorToString(error.reason());
-  if (error_description)
-    *error_description = base::ASCIIToUTF16(err);
 
   if (!error_html)
     return;
diff --git a/android_webview/renderer/aw_content_renderer_client.h b/android_webview/renderer/aw_content_renderer_client.h
index e15b497..9c6906e 100644
--- a/android_webview/renderer/aw_content_renderer_client.h
+++ b/android_webview/renderer/aw_content_renderer_client.h
@@ -40,8 +40,7 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLRequest& failed_request,
                         const blink::WebURLError& error,
-                        std::string* error_html,
-                        base::string16* error_description) override;
+                        std::string* error_html) override;
   unsigned long long VisitedLinkHash(const char* canonical_url,
                                      size_t length) override;
   bool IsLinkVisited(unsigned long long link_hash) override;
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 1a21e3a..89ab61a 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -17,23 +17,26 @@
   ]
 }
 
-group("webview_cts_tests") {
-  _py_files = read_file("//android_webview/tools/run_cts.pydeps", "list lines")
-  deps = [
-    "//android_webview:system_webview_apk",
-  ]
+if (public_android_sdk) {
+  group("webview_cts_tests") {
+    _py_files =
+        read_file("//android_webview/tools/run_cts.pydeps", "list lines")
+    deps = [
+      "//android_webview:system_webview_apk",
+    ]
 
-  data_deps = [
-    "//build/android:logdog_wrapper_py",
-    "//build/android:test_runner_py",
-  ]
+    data_deps = [
+      "//build/android:logdog_wrapper_py",
+      "//build/android:test_runner_py",
+    ]
 
-  # Filter out comments.
-  set_sources_assignment_filter([ "#*" ])
-  sources = _py_files
+    # Filter out comments.
+    set_sources_assignment_filter([ "#*" ])
+    sources = _py_files
 
-  data = sources
-  data += [ "//android_webview/tools/cts_config/" ]
+    data = sources
+    data += [ "//android_webview/tools/cts_config/" ]
+  }
 }
 
 android_apk("webview_instrumentation_apk") {
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 43be2f3..20eb722 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -695,7 +695,7 @@
 
     auth_error_bubble_->ShowErrorBubble(
         container, big_view->auth_user()->password_view() /*anchor_view*/,
-        LoginBubble::kFlagPersistent);
+        true /*show_persistently*/);
   }
 }
 
@@ -814,7 +814,7 @@
   label->SetEnabledColor(SK_ColorWHITE);
   warning_banner_bubble_->ShowErrorBubble(
       label, CurrentBigUserView()->auth_user()->password_view() /*anchor_view*/,
-      LoginBubble::kFlagPersistent);
+      true /*show_persistently*/);
 }
 
 void LockContentsView::OnHideWarningBanner() {
@@ -973,7 +973,7 @@
 
   detachable_base_error_bubble_->ShowErrorBubble(
       label, CurrentBigUserView()->auth_user()->password_view() /*anchor_view*/,
-      LoginBubble::kFlagPersistent);
+      true /*show_persistently*/);
 
   // Remove the focus from the password field, to make user less likely to enter
   // the password without seeing the warning about detachable base change.
@@ -1487,7 +1487,7 @@
     supervised_user_deprecation_bubble_->ShowErrorBubble(
         label,
         CurrentBigUserView()->auth_user()->password_view() /*anchor_view*/,
-        LoginBubble::kFlagPersistent);
+        true /*show_persistently*/);
   }
 
   // The new auth user might have different last used detachable base - make
@@ -1593,7 +1593,7 @@
 
   auth_error_bubble_->ShowErrorBubble(
       container, big_view->auth_user()->password_view() /*anchor_view*/,
-      LoginBubble::kFlagsNone);
+      false /*show_persistently*/);
 }
 
 void LockContentsView::OnEasyUnlockIconHovered() {
diff --git a/ash/login/ui/login_base_bubble_view.cc b/ash/login/ui/login_base_bubble_view.cc
index bc1dd484..6aa1919f 100644
--- a/ash/login/ui/login_base_bubble_view.cc
+++ b/ash/login/ui/login_base_bubble_view.cc
@@ -46,6 +46,10 @@
 
 LoginBaseBubbleView::~LoginBaseBubbleView() = default;
 
+bool LoginBaseBubbleView::IsPersistent() const {
+  return false;
+}
+
 void LoginBaseBubbleView::OnBeforeBubbleWidgetInit(
     views::Widget::InitParams* params,
     views::Widget* widget) const {
diff --git a/ash/login/ui/login_base_bubble_view.h b/ash/login/ui/login_base_bubble_view.h
index 5dfd3ef3..1dbc008 100644
--- a/ash/login/ui/login_base_bubble_view.h
+++ b/ash/login/ui/login_base_bubble_view.h
@@ -21,6 +21,9 @@
                                gfx::NativeView parent_window);
   ~LoginBaseBubbleView() override;
 
+  // Returns whether or not this bubble should show persistently.
+  virtual bool IsPersistent() const;
+
   // views::BubbleDialogDelegateView:
   void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
                                 views::Widget* widget) const override;
diff --git a/ash/login/ui/login_bubble.cc b/ash/login/ui/login_bubble.cc
index 5af28309..23a8f04 100644
--- a/ash/login/ui/login_bubble.cc
+++ b/ash/login/ui/login_bubble.cc
@@ -96,8 +96,10 @@
  public:
   LoginErrorBubbleView(views::View* content,
                        views::View* anchor_view,
-                       aura::Window* container)
-      : LoginBaseBubbleView(anchor_view, container) {
+                       aura::Window* container,
+                       bool show_persistently)
+      : LoginBaseBubbleView(anchor_view, container),
+        show_persistently_(show_persistently) {
     set_anchor_view_insets(
         gfx::Insets(kAnchorViewErrorBubbleVerticalSpacingDp, 0));
 
@@ -125,6 +127,9 @@
 
   ~LoginErrorBubbleView() override = default;
 
+  // LoginBaseBubbleView:
+  bool IsPersistent() const override { return show_persistently_; }
+
   // views::View:
   const char* GetClassName() const override { return "LoginErrorBubbleView"; }
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
@@ -132,6 +137,8 @@
   }
 
  private:
+  bool show_persistently_;
+
   DISALLOW_COPY_AND_ASSIGN(LoginErrorBubbleView);
 };
 
@@ -447,14 +454,14 @@
 
 void LoginBubble::ShowErrorBubble(views::View* content,
                                   views::View* anchor_view,
-                                  uint32_t flags) {
+                                  bool show_persistently) {
   if (bubble_view_)
     CloseImmediately();
 
-  flags_ = flags;
   aura::Window* menu_container = Shell::GetContainer(
       Shell::GetPrimaryRootWindow(), kShellWindowId_MenuContainer);
-  bubble_view_ = new LoginErrorBubbleView(content, anchor_view, menu_container);
+  bubble_view_ = new LoginErrorBubbleView(content, anchor_view, menu_container,
+                                          show_persistently);
 
   Show();
 }
@@ -471,7 +478,6 @@
   if (bubble_view_)
     CloseImmediately();
 
-  flags_ = kFlagsNone;
   bubble_opener_ = bubble_opener;
   bubble_view_ = new LoginUserMenuView(this, username, email, type, is_owner,
                                        anchor_view, show_remove_user,
@@ -490,7 +496,6 @@
   if (bubble_view_)
     CloseImmediately();
 
-  flags_ = kFlagsNone;
   bubble_view_ = new LoginTooltipView(message, anchor_view);
   Show();
 }
@@ -500,7 +505,6 @@
   if (bubble_view_)
     CloseImmediately();
 
-  flags_ = kFlagsNone;
   bubble_opener_ = bubble_opener;
   const bool had_focus = bubble_opener_->HasFocus();
 
@@ -573,7 +577,7 @@
   if (bubble_view_->GetWidget()->IsActive())
     return;
 
-  if (!(flags_ & kFlagPersistent)) {
+  if (!bubble_view_->IsPersistent()) {
     Close();
   }
 }
@@ -597,7 +601,7 @@
   if (gained_focus && bubble_window->Contains(gained_focus))
     return;
 
-  if (!(flags_ & kFlagPersistent))
+  if (!bubble_view_->IsPersistent())
     Close();
 }
 
@@ -638,7 +642,7 @@
       return;
   }
 
-  if (!(flags_ & kFlagPersistent))
+  if (!bubble_view_->IsPersistent())
     Close();
 }
 
@@ -687,7 +691,6 @@
   is_visible_ = false;
   bubble_opener_ = nullptr;
   bubble_view_ = nullptr;
-  flags_ = kFlagsNone;
 }
 
 void LoginBubble::EnsureBubbleInScreen() {
diff --git a/ash/login/ui/login_bubble.h b/ash/login/ui/login_bubble.h
index 48bb440..e7f77941 100644
--- a/ash/login/ui/login_bubble.h
+++ b/ash/login/ui/login_bubble.h
@@ -40,12 +40,6 @@
 
   static const int kUserMenuRemoveUserButtonIdForTest;
 
-  // Flags passed to ShowErrorBubble().
-  static constexpr uint32_t kFlagsNone = 0;
-  // If set, the shown error bubble will not be closed due to an unrelated user
-  // action - e.g. the bubble will not be closed if the user starts typing.
-  static constexpr uint32_t kFlagPersistent = 1 << 0;
-
   LoginBubble();
   ~LoginBubble() override;
 
@@ -53,7 +47,7 @@
   // |anchor_view| is the anchor for placing the bubble view.
   void ShowErrorBubble(views::View* content,
                        views::View* anchor_view,
-                       uint32_t flags);
+                       bool show_persistently);
 
   // Shows a user menu bubble.
   // |anchor_view| is the anchor for placing the bubble view.
@@ -128,9 +122,6 @@
   // Repositions the bubble view if it extends too far right or down.
   void EnsureBubbleInScreen();
 
-  // Flags passed to ShowErrorBubble().
-  uint32_t flags_ = kFlagsNone;
-
   LoginBaseBubbleView* bubble_view_ = nullptr;
 
   // A button that could open/close the bubble.
diff --git a/ash/login/ui/login_bubble_unittest.cc b/ash/login/ui/login_bubble_unittest.cc
index 1934f6a..b93cba5 100644
--- a/ash/login/ui/login_bubble_unittest.cc
+++ b/ash/login/ui/login_bubble_unittest.cc
@@ -288,7 +288,7 @@
 
   EXPECT_FALSE(bubble_->IsVisible());
   views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
-  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  bubble_->ShowErrorBubble(error_text, container_, false /*show_persistently*/);
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Verifies that key event on a view other than error closes the error bubble.
@@ -302,7 +302,7 @@
 
   EXPECT_FALSE(bubble_->IsVisible());
   views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
-  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  bubble_->ShowErrorBubble(error_text, container_, false /*show_persistently*/);
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Verifies that mouse event on the bubble itself won't close the bubble.
@@ -322,7 +322,7 @@
 
   EXPECT_FALSE(bubble_->IsVisible());
   views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
-  bubble_->ShowErrorBubble(error_text, container_, LoginBubble::kFlagsNone);
+  bubble_->ShowErrorBubble(error_text, container_, false /*show_persistently*/);
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Verifies that gesture event on the bubble itself won't close the bubble.
@@ -340,8 +340,7 @@
 
   EXPECT_FALSE(bubble_->IsVisible());
   views::Label* error_text = new views::Label(base::ASCIIToUTF16("Error text"));
-  bubble_->ShowErrorBubble(error_text, container_,
-                           LoginBubble::kFlagPersistent);
+  bubble_->ShowErrorBubble(error_text, container_, true /*show_persistently*/);
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Verifies that mouse event on the bubble itself won't close the bubble.
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index 700c5da..1a5abdd 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -18,7 +18,6 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/strings/utf_string_conversions.h"
-#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -75,7 +74,6 @@
 
 constexpr char kMonitoringWarningClassName[] = "MonitoringWarning";
 constexpr int kSpacingBetweenMonitoringWarningIconAndLabelDp = 8;
-constexpr int kMonitoringWarningIconSizeDp = 20;
 
 views::Label* CreateLabel(const base::string16& text, SkColor color) {
   auto* label = new views::Label(text);
@@ -181,8 +179,7 @@
   DISALLOW_COPY_AND_ASSIGN(SelectionButtonView);
 };
 
-// Container for the device monitoring warning.  Contains the warning icon on
-// the left side, and text on the right side.
+// Container for the device monitoring warning.
 class MonitoringWarningView : public NonAccessibleView {
  public:
   MonitoringWarningView() : NonAccessibleView(kMonitoringWarningClassName) {
@@ -190,13 +187,6 @@
         views::BoxLayout::kHorizontal, gfx::Insets(),
         kSpacingBetweenMonitoringWarningIconAndLabelDp));
 
-    views::ImageView* image = new views::ImageView();
-    image->SetImage(gfx::CreateVectorIcon(
-        vector_icons::kWarningIcon, kMonitoringWarningIconSizeDp, SK_ColorRED));
-    image->SetPreferredSize(
-        gfx::Size(kMonitoringWarningIconSizeDp, kMonitoringWarningIconSizeDp));
-    AddChildView(image);
-
     const base::string16 label_text = l10n_util::GetStringUTF16(
         IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_WARNING);
     views::Label* label = CreateLabel(label_text, SK_ColorWHITE);
diff --git a/ash/media/media_notification_controller.cc b/ash/media/media_notification_controller.cc
index c65181a..3ddc2a27 100644
--- a/ash/media/media_notification_controller.cc
+++ b/ash/media/media_notification_controller.cc
@@ -67,13 +67,19 @@
 
 MediaNotificationController::~MediaNotificationController() = default;
 
-void MediaNotificationController::OnFocusGained(
-    media_session::mojom::MediaSessionInfoPtr session_info,
-    media_session::mojom::AudioFocusType type) {
+void MediaNotificationController::OnActiveSessionChanged(
+    media_session::mojom::AudioFocusRequestStatePtr session) {
+  // Hide the notification if the active session is null.
+  if (session.is_null()) {
+    message_center::MessageCenter::Get()->RemoveNotification(
+        kMediaSessionNotificationId, false);
+    return;
+  }
+
   if (IsMediaSessionNotificationVisible())
     return;
 
-  session_info_ = std::move(session_info);
+  session_info_ = std::move(session->session_info);
 
   std::unique_ptr<message_center::Notification> notification =
       ash::CreateSystemNotification(
@@ -101,15 +107,6 @@
       std::move(notification));
 }
 
-void MediaNotificationController::OnFocusLost(
-    media_session::mojom::MediaSessionInfoPtr session_info) {
-  if (!IsMediaSessionNotificationVisible())
-    return;
-
-  message_center::MessageCenter::Get()->RemoveNotification(
-      kMediaSessionNotificationId, false);
-}
-
 void MediaNotificationController::MediaSessionInfoChanged(
     media_session::mojom::MediaSessionInfoPtr session_info) {
   session_info_ = std::move(session_info);
diff --git a/ash/media/media_notification_controller.h b/ash/media/media_notification_controller.h
index 130bd23..927f19b3 100644
--- a/ash/media/media_notification_controller.h
+++ b/ash/media/media_notification_controller.h
@@ -34,9 +34,11 @@
 
   // media_session::mojom::AudioFocusObserver:
   void OnFocusGained(media_session::mojom::MediaSessionInfoPtr session_info,
-                     media_session::mojom::AudioFocusType type) override;
+                     media_session::mojom::AudioFocusType type) override {}
   void OnFocusLost(
-      media_session::mojom::MediaSessionInfoPtr session_info) override;
+      media_session::mojom::MediaSessionInfoPtr session_info) override {}
+  void OnActiveSessionChanged(
+      media_session::mojom::AudioFocusRequestStatePtr session) override;
 
   // media_session::mojom::MediaSessionObserver:
   void MediaSessionInfoChanged(
diff --git a/ash/media/media_notification_controller_unittest.cc b/ash/media/media_notification_controller_unittest.cc
index 5e34807..3fc8739 100644
--- a/ash/media/media_notification_controller_unittest.cc
+++ b/ash/media/media_notification_controller_unittest.cc
@@ -17,8 +17,7 @@
 
 namespace ash {
 
-using media_session::mojom::AudioFocusType;
-using media_session::mojom::MediaSessionInfo;
+using media_session::mojom::AudioFocusRequestState;
 
 namespace {
 
@@ -56,43 +55,43 @@
   DISALLOW_COPY_AND_ASSIGN(MediaNotificationControllerTest);
 };
 
-TEST_F(MediaNotificationControllerTest, OnFocusGainedLost) {
+TEST_F(MediaNotificationControllerTest, OnActiveSessionChanged) {
   EXPECT_FALSE(IsMediaNotificationShown());
   EXPECT_EQ(0, GetVisibleNotificationCount());
   EXPECT_EQ(0, GetPopupNotificationCount());
 
-  Shell::Get()->media_notification_controller()->OnFocusGained(
-      MediaSessionInfo::New(), AudioFocusType::kGain);
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      AudioFocusRequestState::New());
   EXPECT_TRUE(IsMediaNotificationShown());
   EXPECT_EQ(1, GetVisibleNotificationCount());
   EXPECT_EQ(0, GetPopupNotificationCount());
 
-  Shell::Get()->media_notification_controller()->OnFocusGained(
-      MediaSessionInfo::New(), AudioFocusType::kGain);
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      AudioFocusRequestState::New());
   EXPECT_TRUE(IsMediaNotificationShown());
   EXPECT_EQ(1, GetVisibleNotificationCount());
   EXPECT_EQ(0, GetPopupNotificationCount());
 
-  Shell::Get()->media_notification_controller()->OnFocusLost(
-      MediaSessionInfo::New());
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      nullptr);
   EXPECT_FALSE(IsMediaNotificationShown());
   EXPECT_EQ(0, GetVisibleNotificationCount());
   EXPECT_EQ(0, GetPopupNotificationCount());
 }
 
-TEST_F(MediaNotificationControllerTest, OnFocusLost_Noop) {
+TEST_F(MediaNotificationControllerTest, OnActiveSessionChanged_Noop) {
   EXPECT_FALSE(IsMediaNotificationShown());
 
-  Shell::Get()->media_notification_controller()->OnFocusLost(
-      MediaSessionInfo::New());
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      nullptr);
   EXPECT_FALSE(IsMediaNotificationShown());
 }
 
 TEST_F(MediaNotificationControllerTest, NotificationHasCustomViewType) {
   EXPECT_FALSE(IsMediaNotificationShown());
 
-  Shell::Get()->media_notification_controller()->OnFocusGained(
-      MediaSessionInfo::New(), AudioFocusType::kGain);
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      AudioFocusRequestState::New());
   message_center::Notification* notification =
       message_center::MessageCenter::Get()->FindVisibleNotificationById(
           kMediaSessionNotificationId);
diff --git a/ash/media/media_notification_view_unittest.cc b/ash/media/media_notification_view_unittest.cc
index ff521b2..14c9515b 100644
--- a/ash/media/media_notification_view_unittest.cc
+++ b/ash/media/media_notification_view_unittest.cc
@@ -78,8 +78,11 @@
             base::Unretained(this)));
 
     // Show the notification.
-    Shell::Get()->media_notification_controller()->OnFocusGained(
-        std::move(session_info), media_session::mojom::AudioFocusType::kGain);
+    media_session::mojom::AudioFocusRequestStatePtr session(
+        media_session::mojom::AudioFocusRequestState::New());
+    session->session_info = std::move(session_info);
+    Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+        std::move(session));
 
     message_center::Notification* notification =
         message_center::MessageCenter::Get()->FindVisibleNotificationById(
@@ -229,7 +232,7 @@
   EXPECT_EQ(0, media_controller()->toggle_suspend_resume_count());
 }
 
-TEST_F(MediaNotificationViewTest, PlayToggle_FromFocusGain) {
+TEST_F(MediaNotificationViewTest, PlayToggle_FromActiveSessionChanged) {
   {
     views::ToggleImageButton* button =
         static_cast<views::ToggleImageButton*>(button_row()->child_at(1));
@@ -237,8 +240,8 @@
     EXPECT_FALSE(button->toggled_for_testing());
   }
 
-  Shell::Get()->media_notification_controller()->OnFocusLost(
-      media_session::mojom::MediaSessionInfo::New());
+  Shell::Get()->media_notification_controller()->OnActiveSessionChanged(
+      nullptr);
 
   // Disable the tray and run the loop to make sure that the existing view is
   // destroyed.
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 47ec75a..7183df4 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -46,9 +46,6 @@
 const base::Feature kNotificationScrollBar{"NotificationScrollBar",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kOverviewSwipeToClose{"OverviewSwipeToClose",
-                                          base::FEATURE_ENABLED_BY_DEFAULT};
-
 const base::Feature kTrilinearFiltering{"TrilinearFiltering",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index 25f14c4..96b16001 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -63,11 +63,6 @@
 // Enables notification scroll bar in UnifiedSystemTray.
 ASH_PUBLIC_EXPORT extern const base::Feature kNotificationScrollBar;
 
-// Enables swipe to close in overview mode.
-// TODO(sammiequon): Remove this after the feature is fully launched.
-// https://crbug.com/828646.
-ASH_PUBLIC_EXPORT extern const base::Feature kOverviewSwipeToClose;
-
 // Enables trilinear filtering.
 ASH_PUBLIC_EXPORT extern const base::Feature kTrilinearFiltering;
 
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index 24ada41..77ca82fb 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -114,10 +114,6 @@
   return wm::GetWindowState(window)->IsMaximizedOrFullscreenOrPinned();
 }
 
-bool IsOverviewSwipeToCloseEnabled() {
-  return base::FeatureList::IsEnabled(features::kOverviewSwipeToClose);
-}
-
 void FadeInWidgetAndMaybeSlideOnEnter(views::Widget* widget,
                                       OverviewAnimationType animation_type,
                                       bool slide) {
diff --git a/ash/wm/overview/overview_utils.h b/ash/wm/overview/overview_utils.h
index 3109019a..a93f7c1a 100644
--- a/ash/wm/overview/overview_utils.h
+++ b/ash/wm/overview/overview_utils.h
@@ -27,8 +27,6 @@
 // Returns true if |window| can cover available workspace.
 bool CanCoverAvailableWorkspace(aura::Window* window);
 
-bool IsOverviewSwipeToCloseEnabled();
-
 // Fades |widget| to opacity one with the enter overview settings. Additionally
 // place |widget| closer to the top of screen and slide it down if |slide| is
 // true.
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index 57c623d..0ec2e11f 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -75,8 +75,7 @@
       return;
     }
 
-    if (IsOverviewSwipeToCloseEnabled() &&
-        std::abs(distance.x()) < std::abs(distance.y())) {
+    if (std::abs(distance.x()) < std::abs(distance.y())) {
       current_drag_behavior_ = DragBehavior::kDragToClose;
       original_opacity_ = item_->GetOpacity();
       window_selector_->GetGridWithRootWindow(item_->root_window())
diff --git a/ash/wm/overview/window_selector_unittest.cc b/ash/wm/overview/window_selector_unittest.cc
index 293e5f3..1cc8ebd 100644
--- a/ash/wm/overview/window_selector_unittest.cc
+++ b/ash/wm/overview/window_selector_unittest.cc
@@ -50,7 +50,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/user_action_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/focus_client.h"
 #include "ui/aura/client/window_types.h"
@@ -3573,9 +3572,6 @@
 TEST_F(SplitViewWindowSelectorTest, OverviewDragControllerBehavior) {
   Shell::Get()->aura_env()->set_throttle_input_on_resize_for_testing(false);
 
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   std::unique_ptr<aura::Window> window1 = CreateTestWindow();
   std::unique_ptr<aura::Window> window2 = CreateTestWindow();
 
@@ -3617,9 +3613,6 @@
 // Verify that if the window item has been dragged enough vertically, the window
 // will be closed.
 TEST_F(SplitViewWindowSelectorTest, DragToClose) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   // This test requires a widget.
   const gfx::Rect bounds(400, 400);
   std::unique_ptr<views::Widget> widget1(CreateWindowWidget(bounds));
@@ -3651,9 +3644,6 @@
 // Verify that if the window item has been flung enough vertically, the window
 // will be closed.
 TEST_F(SplitViewWindowSelectorTest, FlingToClose) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   // This test requires a widget.
   const gfx::Rect bounds(400, 400);
   std::unique_ptr<views::Widget> widget1(CreateWindowWidget(bounds));
@@ -3694,9 +3684,6 @@
 // we still only have one row, so the other items should nudge while the item is
 // being dragged.
 TEST_F(SplitViewWindowSelectorTest, BasicNudging) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   // Set up three equal windows, which take up one row on the overview grid.
   // When one of them is deleted we are still left with all the windows on one
   // row.
@@ -3741,9 +3728,6 @@
 // if the item to be deleted results in the overview grid to change number of
 // rows.
 TEST_F(SplitViewWindowSelectorTest, NoNudgingWhenNumRowsChange) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   // Set up four equal windows, which would split into two rows in overview
   // mode. Removing one window would leave us with three windows, which only
   // takes a single row in overview.
@@ -3779,9 +3763,6 @@
 // from the previous row to drop down to the current row, thus causing the items
 // to the right of the item to be shifted right, which is visually unacceptable.
 TEST_F(SplitViewWindowSelectorTest, NoNudgingWhenLastItemOnPreviousRowDrops) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kOverviewSwipeToClose);
-
   // Set up five equal windows, which would split into two rows in overview
   // mode. Removing one window would cause the rows to rearrange, with the third
   // item dropping down from the first row to the second row. Create the windows
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 521bca5..53ddb42 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -79,6 +79,11 @@
 void TabletModeWindowDragDelegate::StartWindowDrag(
     aura::Window* dragged_window,
     const gfx::Point& location_in_screen) {
+  // TODO(oshima): Consider doing the same for normal window dragging as well
+  // crbug.com/904631.
+  DCHECK(!occlusion_excluder_);
+  occlusion_excluder_.emplace(dragged_window);
+
   dragged_window_ = dragged_window;
   initial_location_in_screen_ = location_in_screen;
 
@@ -199,6 +204,7 @@
 
   // For child class to do its special handling if any.
   EndedWindowDrag(location_in_screen);
+  occlusion_excluder_.reset();
   dragged_window_ = nullptr;
   did_move_ = false;
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
index 87592eb..4d9880c 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.h
@@ -11,6 +11,8 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/wm_toplevel_window_event_handler.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "ui/aura/window_occlusion_tracker.h"
 #include "ui/wm/core/shadow_types.h"
 
 namespace ash {
@@ -141,6 +143,9 @@
   // the window is moved, it will stay as 'moved'.
   bool did_move_ = false;
 
+  base::Optional<aura::WindowOcclusionTracker::ScopedExclude>
+      occlusion_excluder_;
+
   base::WeakPtrFactory<TabletModeWindowDragDelegate> weak_ptr_factory_;
 
  private:
diff --git a/base/BUILD.gn b/base/BUILD.gn
index f5d50e9..a60ced7 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2161,7 +2161,7 @@
 }
 
 if (is_win || is_mac) {
-  if (current_cpu == "x64") {
+  if (current_cpu == "x64" || (current_cpu == "arm64" && is_win)) {
     # Must be a shared library so that it can be unloaded during testing.
     shared_library("base_profiler_test_support_library") {
       sources = [
@@ -2957,6 +2957,7 @@
       "android/java/src/org/chromium/base/PackageUtils.java",
       "android/java/src/org/chromium/base/PathService.java",
       "android/java/src/org/chromium/base/PathUtils.java",
+      "android/java/src/org/chromium/base/PiiElider.java",
       "android/java/src/org/chromium/base/PowerMonitor.java",
       "android/java/src/org/chromium/base/Promise.java",
       "android/java/src/org/chromium/base/SecureRandomInitializer.java",
@@ -3185,6 +3186,7 @@
       "android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java",
       "android/junit/src/org/chromium/base/LogTest.java",
       "android/junit/src/org/chromium/base/NonThreadSafeTest.java",
+      "android/junit/src/org/chromium/base/PiiEliderTest.java",
       "android/junit/src/org/chromium/base/PromiseTest.java",
       "android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java",
       "android/junit/src/org/chromium/base/process_launcher/ChildConnectionAllocatorTest.java",
diff --git a/base/allocator/allocator_shim_unittest.cc b/base/allocator/allocator_shim_unittest.cc
index 20a2dace..dea8d6b8 100644
--- a/base/allocator/allocator_shim_unittest.cc
+++ b/base/allocator/allocator_shim_unittest.cc
@@ -24,6 +24,7 @@
 
 #if defined(OS_WIN)
 #include <windows.h>
+#include <malloc.h>
 #elif defined(OS_MACOSX)
 #include <malloc/malloc.h>
 #include "base/allocator/allocator_interception_mac.h"
diff --git a/base/android/java/src/org/chromium/base/JavaExceptionReporter.java b/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
index f192f78c..91c491be 100644
--- a/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
+++ b/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
@@ -51,7 +51,7 @@
     @UiThread
     public static void reportStackTrace(String stackTrace) {
         assert ThreadUtils.runningOnUiThread();
-        nativeReportJavaStackTrace(stackTrace);
+        nativeReportJavaStackTrace(PiiElider.sanitizeStacktrace(stackTrace));
     }
 
     @CalledByNative
diff --git a/base/android/java/src/org/chromium/base/PiiElider.java b/base/android/java/src/org/chromium/base/PiiElider.java
new file mode 100644
index 0000000..8474f0f
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PiiElider.java
@@ -0,0 +1,186 @@
+// 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.
+
+package org.chromium.base;
+
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides public methods for detecting and eliding sensitive PII.
+ */
+public class PiiElider {
+    private static final String EMAIL_ELISION = "XXX@EMAIL.ELIDED";
+
+    private static final String URL_ELISION = "HTTP://WEBADDRESS.ELIDED";
+
+    private static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+
+    private static final Pattern IP_ADDRESS = Pattern.compile(
+            "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+            + "|[1-9][0-9]|[0-9]))");
+
+    private static final String IRI =
+            "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
+
+    private static final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+    private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
+    private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
+
+    private static final Pattern DOMAIN_NAME =
+            Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
+
+    private static final Pattern LIKELY_EXCEPTION_LOG =
+            Pattern.compile("\\sat\\sorg\\.chromium\\.[^ ]+.");
+
+    private static final Pattern WEB_URL =
+            Pattern.compile("(?:\\b|^)((?:(http|https|Http|Https|rtsp|Rtsp):"
+                    + "\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+                    + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+                    + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+                    + "(?:" + DOMAIN_NAME + ")"
+                    + "(?:\\:\\d{1,5})?)"
+                    + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~"
+                    + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+                    + "(?:\\b|$)");
+
+    private static final String IP_ELISION = "1.2.3.4";
+    private static final String MAC_ELISION = "01:23:45:67:89:AB";
+    private static final String CONSOLE_ELISION = "[ELIDED:CONSOLE(0)] ELIDED CONSOLE MESSAGE";
+
+    private static final Pattern MAC_ADDRESS =
+            Pattern.compile("([0-9a-fA-F]{2}[-:]+){5}[0-9a-fA-F]{2}");
+
+    private static final Pattern CONSOLE_MSG = Pattern.compile("\\[\\w*:CONSOLE.*\\].*");
+
+    private static final String[] APP_NAMESPACE = new String[] {"org.chromium.", "com.google."};
+
+    private static final String[] SYSTEM_NAMESPACE = new String[] {"android.accessibilityservice",
+            "android.accounts", "android.animation", "android.annotation", "android.app",
+            "android.appwidget", "android.bluetooth", "android.content", "android.database",
+            "android.databinding", "android.drm", "android.gesture", "android.graphics",
+            "android.hardware", "android.inputmethodservice", "android.location", "android.media",
+            "android.mtp", "android.net", "android.nfc", "android.opengl", "android.os",
+            "android.preference", "android.print", "android.printservice", "android.provider",
+            "android.renderscript", "android.sax", "android.security", "android.service",
+            "android.speech", "android.support", "android.system", "android.telecom",
+            "android.telephony", "android.test", "android.text", "android.transition",
+            "android.util", "android.view", "android.webkit", "android.widget", "com.android.",
+            "dalvik.", "java.", "javax.", "org.apache.", "org.json.", "org.w3c.dom.", "org.xml.",
+            "org.xmlpull."};
+
+    /**
+     * Elides any emails in the specified {@link String} with
+     * {@link #EMAIL_ELISION}.
+     *
+     * @param original String potentially containing emails.
+     * @return String with elided emails.
+     */
+    public static String elideEmail(String original) {
+        return Patterns.EMAIL_ADDRESS.matcher(original).replaceAll(EMAIL_ELISION);
+    }
+
+    /**
+     * Elides any URLs in the specified {@link String} with
+     * {@link #URL_ELISION}.
+     *
+     * @param original String potentially containing URLs.
+     * @return String with elided URLs.
+     */
+    public static String elideUrl(String original) {
+        // Url-matching is fussy. If something looks like an exception message, just return.
+        if (LIKELY_EXCEPTION_LOG.matcher(original).find()) return original;
+        StringBuilder buffer = new StringBuilder(original);
+        Matcher matcher = WEB_URL.matcher(buffer);
+        int start = 0;
+        while (matcher.find(start)) {
+            start = matcher.start();
+            int end = matcher.end();
+            String url = buffer.substring(start, end);
+            if (!likelyToBeAppNamespace(url) && !likelyToBeSystemNamespace(url)) {
+                buffer.replace(start, end, URL_ELISION);
+                end = start + URL_ELISION.length();
+                matcher = WEB_URL.matcher(buffer);
+            }
+            start = end;
+        }
+        return buffer.toString();
+    }
+
+    private static boolean likelyToBeAppNamespace(String url) {
+        for (String ns : APP_NAMESPACE) {
+            if (url.startsWith(ns)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean likelyToBeSystemNamespace(String url) {
+        for (String ns : SYSTEM_NAMESPACE) {
+            if (url.startsWith(ns)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Elides any IP addresses in the specified {@link String} with
+     * {@link #IP_ELISION}.
+     *
+     * @param original String potentially containing IPs.
+     * @return String with elided IPs.
+     */
+    public static String elideIp(String original) {
+        return Patterns.IP_ADDRESS.matcher(original).replaceAll(IP_ELISION);
+    }
+
+    /**
+     * Elides any MAC addresses in the specified {@link String} with
+     * {@link #MAC_ELISION}.
+     *
+     * @param original String potentially containing MACs.
+     * @return String with elided MACs.
+     */
+    public static String elideMac(String original) {
+        return MAC_ADDRESS.matcher(original).replaceAll(MAC_ELISION);
+    }
+
+    /**
+     * Elides any console messages in the specified {@link String} with
+     * {@link #CONSOLE_ELISION}.
+     *
+     * @param original String potentially containing console messages.
+     * @return String with elided console messages.
+     */
+    public static String elideConsole(String original) {
+        return CONSOLE_MSG.matcher(original).replaceAll(CONSOLE_ELISION);
+    }
+
+    /**
+     * Elides any URL in the exception messages contained inside a stacktrace with
+     * {@link #URL_ELISION}.
+     *
+     * @param stacktrace Multiline stacktrace as a string.
+     * @return Stacktrace with elided URLs.
+     */
+    public static String sanitizeStacktrace(String stacktrace) {
+        String[] frames = stacktrace.split("\\n");
+        // Sanitize first stacktrace line which contains the exception message.
+        frames[0] = elideUrl(frames[0]);
+        for (int i = 1; i < frames.length; i++) {
+            // Nested exceptions should also have their message sanitized.
+            if (frames[i].startsWith("Caused by:")) {
+                frames[i] = elideUrl(frames[i]);
+            }
+        }
+        return TextUtils.join("\n", frames);
+    }
+}
diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc
index 598e884..aae65a65 100644
--- a/base/android/jni_android.cc
+++ b/base/android/jni_android.cc
@@ -250,53 +250,30 @@
 }
 
 std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
-  ScopedJavaLocalRef<jclass> throwable_clazz =
-      GetClass(env, "java/lang/Throwable");
-  jmethodID throwable_printstacktrace =
-      MethodID::Get<MethodID::TYPE_INSTANCE>(
-          env, throwable_clazz.obj(), "printStackTrace",
-          "(Ljava/io/PrintStream;)V");
+  ScopedJavaLocalRef<jclass> log_clazz = GetClass(env, "android/util/Log");
+  jmethodID log_getstacktracestring = MethodID::Get<MethodID::TYPE_STATIC>(
+      env, log_clazz.obj(), "getStackTraceString",
+      "(Ljava/lang/Throwable;)Ljava/lang/String;");
 
-  // Create an instance of ByteArrayOutputStream.
-  ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz =
-      GetClass(env, "java/io/ByteArrayOutputStream");
-  jmethodID bytearray_output_stream_constructor =
-      MethodID::Get<MethodID::TYPE_INSTANCE>(
-          env, bytearray_output_stream_clazz.obj(), "<init>", "()V");
-  jmethodID bytearray_output_stream_tostring =
-      MethodID::Get<MethodID::TYPE_INSTANCE>(
-          env, bytearray_output_stream_clazz.obj(), "toString",
-          "()Ljava/lang/String;");
-  ScopedJavaLocalRef<jobject> bytearray_output_stream(env,
-      env->NewObject(bytearray_output_stream_clazz.obj(),
-                     bytearray_output_stream_constructor));
-  CheckException(env);
-
-  // Create an instance of PrintStream.
-  ScopedJavaLocalRef<jclass> printstream_clazz =
-      GetClass(env, "java/io/PrintStream");
-  jmethodID printstream_constructor =
-      MethodID::Get<MethodID::TYPE_INSTANCE>(
-          env, printstream_clazz.obj(), "<init>",
-          "(Ljava/io/OutputStream;)V");
-  ScopedJavaLocalRef<jobject> printstream(env,
-      env->NewObject(printstream_clazz.obj(), printstream_constructor,
-                     bytearray_output_stream.obj()));
-  CheckException(env);
-
-  // Call Throwable.printStackTrace(PrintStream)
-  env->CallVoidMethod(java_throwable, throwable_printstacktrace,
-      printstream.obj());
-  CheckException(env);
-
-  // Call ByteArrayOutputStream.toString()
+  // Call Log.getStackTraceString()
   ScopedJavaLocalRef<jstring> exception_string(
-      env, static_cast<jstring>(
-          env->CallObjectMethod(bytearray_output_stream.obj(),
-                                bytearray_output_stream_tostring)));
+      env, static_cast<jstring>(env->CallStaticObjectMethod(
+               log_clazz.obj(), log_getstacktracestring, java_throwable)));
   CheckException(env);
 
-  return ConvertJavaStringToUTF8(exception_string);
+  ScopedJavaLocalRef<jclass> piielider_clazz =
+      GetClass(env, "org/chromium/base/PiiElider");
+  jmethodID piielider_sanitize_stacktrace =
+      MethodID::Get<MethodID::TYPE_STATIC>(
+          env, piielider_clazz.obj(), "sanitizeStacktrace",
+          "(Ljava/lang/String;)Ljava/lang/String;");
+  ScopedJavaLocalRef<jstring> sanitized_exception_string(
+      env, static_cast<jstring>(env->CallStaticObjectMethod(
+               piielider_clazz.obj(), piielider_sanitize_stacktrace,
+               exception_string.obj())));
+  CheckException(env);
+
+  return ConvertJavaStringToUTF8(sanitized_exception_string);
 }
 
 #if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
diff --git a/base/android/junit/src/org/chromium/base/PiiEliderTest.java b/base/android/junit/src/org/chromium/base/PiiEliderTest.java
new file mode 100644
index 0000000..d93d228
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/PiiEliderTest.java
@@ -0,0 +1,136 @@
+// 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.
+
+package org.chromium.base;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * junit tests for {@link PiiElider}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class PiiEliderTest {
+    private static final int MAX_LINES = 5;
+
+    @Test
+    public void testElideEmail() {
+        String original = "email me at someguy@mailservice.com";
+        String expected = "email me at XXX@EMAIL.ELIDED";
+        assertEquals(expected, PiiElider.elideEmail(original));
+    }
+
+    @Test
+    public void testElideUrl() {
+        String original = "file bugs at crbug.com";
+        String expected = "file bugs at HTTP://WEBADDRESS.ELIDED";
+        assertEquals(expected, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl2() {
+        String original = "exception at org.chromium.base.PiiEliderTest";
+        assertEquals(original, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl3() {
+        String original = "file bugs at crbug.com or code.google.com";
+        String expected = "file bugs at HTTP://WEBADDRESS.ELIDED or HTTP://WEBADDRESS.ELIDED";
+        assertEquals(expected, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl4() {
+        String original = "test shorturl.com !!!";
+        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
+        assertEquals(expected, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl5() {
+        String original = "test just.the.perfect.len.url !!!";
+        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
+        assertEquals(expected, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl6() {
+        String original = "test a.very.very.very.very.very.long.url !!!";
+        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
+        assertEquals(expected, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl7() {
+        String original = " at android.content.Intent \n at java.util.ArrayList";
+        assertEquals(original, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl8() {
+        String original = "exception at org.chromium.chrome.browser.compositor.scene_layer."
+                + "TabListSceneLayer.nativeUpdateLayer(Native Method)";
+        assertEquals(original, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideUrl9() {
+        String original = "I/dalvikvm( 5083): at org.chromium.chrome.browser.compositor."
+                + "scene_layer.TabListSceneLayer.nativeUpdateLayer(Native Method)";
+        assertEquals(original, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testDontElideFileSuffixes() {
+        String original = "chromium_android_linker.so";
+        assertEquals(original, PiiElider.elideUrl(original));
+    }
+
+    @Test
+    public void testElideIp() {
+        String original = "traceroute 127.0.0.1";
+        String expected = "traceroute 1.2.3.4";
+        assertEquals(expected, PiiElider.elideIp(original));
+    }
+
+    @Test
+    public void testElideMac1() {
+        String original = "MAC: AB-AB-AB-AB-AB-AB";
+        String expected = "MAC: 01:23:45:67:89:AB";
+        assertEquals(expected, PiiElider.elideMac(original));
+    }
+
+    @Test
+    public void testElideMac2() {
+        String original = "MAC: AB:AB:AB:AB:AB:AB";
+        String expected = "MAC: 01:23:45:67:89:AB";
+        assertEquals(expected, PiiElider.elideMac(original));
+    }
+
+    @Test
+    public void testElideConsole() {
+        String original = "I/chromium(123): [INFO:CONSOLE(2)] hello!";
+        String expected = "I/chromium(123): [ELIDED:CONSOLE(0)] ELIDED CONSOLE MESSAGE";
+        assertEquals(expected, PiiElider.elideConsole(original));
+    }
+
+    @Test
+    public void testElideUrlInStacktrace() {
+        String original = "java.lang.RuntimeException: Outer Exception crbug.com/12345\n"
+                + "  at org.chromium.base.PiiElider.sanitizeStacktrace (PiiElider.java:120)\n"
+                + "Caused by: java.lang.NullPointerException: Inner Exception shorturl.com/bxyj5";
+        String expected = "java.lang.RuntimeException: Outer Exception HTTP://WEBADDRESS.ELIDED\n"
+                + "  at org.chromium.base.PiiElider.sanitizeStacktrace (PiiElider.java:120)\n"
+                + "Caused by: java.lang.NullPointerException: Inner Exception "
+                + "HTTP://WEBADDRESS.ELIDED";
+        assertEquals(expected, PiiElider.sanitizeStacktrace(original));
+    }
+}
diff --git a/base/android/proguard/chromium_code.flags b/base/android/proguard/chromium_code.flags
index 19347af..8a3ec58 100644
--- a/base/android/proguard/chromium_code.flags
+++ b/base/android/proguard/chromium_code.flags
@@ -38,6 +38,9 @@
 -keepclasseswithmembers class * {
   @org.chromium.base.annotations.UsedByReflection <fields>;
 }
+-keepclasseswithmembers,includedescriptorclasses class * {
+  native <methods>;
+}
 
 # Remove methods annotated with this if their return value is unused.
 -assumenosideeffects class ** {
diff --git a/base/android/proguard/explicit_jni_registration.flags b/base/android/proguard/explicit_jni_registration.flags
deleted file mode 100644
index f95ea5d..0000000
--- a/base/android/proguard/explicit_jni_registration.flags
+++ /dev/null
@@ -1,10 +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.
-
-# -keepclasseswithmembers rather than -keepclasseswithmembernames to avoid
-# shrinking of unused native methods. Explicit JNI registration requires even
-# unused classes to be present during RegisterNatives().
--keepclasseswithmembers,includedescriptorclasses class * {
-  native <methods>;
-}
diff --git a/base/android/proguard/implicit_jni_registration.flags b/base/android/proguard/implicit_jni_registration.flags
deleted file mode 100644
index 2495f7b..0000000
--- a/base/android/proguard/implicit_jni_registration.flags
+++ /dev/null
@@ -1,9 +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.
-
-# -keepclasseswithmembernames rather than -keepclasseswithmembers to allow
-# shrinking of unused native methods.
--keepclasseswithmembernames,includedescriptorclasses class * {
-  native <methods>;
-}
diff --git a/base/profiler/native_stack_sampler_win.cc b/base/profiler/native_stack_sampler_win.cc
index c4ed209a..d7d6c31 100644
--- a/base/profiler/native_stack_sampler_win.cc
+++ b/base/profiler/native_stack_sampler_win.cc
@@ -24,6 +24,7 @@
 #include "base/stl_util.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -157,10 +158,19 @@
                                   uintptr_t bottom,
                                   CONTEXT* context,
                                   void* stack_copy) {
-#if defined(_WIN64)
+#if defined(ARCH_CPU_64_BITS)
   DWORD64 CONTEXT::*const nonvolatile_registers[] = {
+#if defined(ARCH_CPU_X86_64)
       &CONTEXT::R12, &CONTEXT::R13, &CONTEXT::R14, &CONTEXT::R15, &CONTEXT::Rdi,
-      &CONTEXT::Rsi, &CONTEXT::Rbx, &CONTEXT::Rbp, &CONTEXT::Rsp};
+      &CONTEXT::Rsi, &CONTEXT::Rbx, &CONTEXT::Rbp, &CONTEXT::Rsp
+#elif defined(ARCH_CPU_ARM64)
+      &CONTEXT::X19, &CONTEXT::X20, &CONTEXT::X21, &CONTEXT::X22, &CONTEXT::X23,
+      &CONTEXT::X24, &CONTEXT::X25, &CONTEXT::X26, &CONTEXT::X27, &CONTEXT::X28,
+      &CONTEXT::Fp, &CONTEXT::Lr
+#else
+#error Unsupported Windows 64-bit Arch
+#endif
+  };
 
   // Rewrite pointers in the context.
   for (size_t i = 0; i < size(nonvolatile_registers); ++i) {
@@ -213,9 +223,9 @@
   stack->reserve(128);
 
   Win32StackFrameUnwinder frame_unwinder;
-  while (context->Rip) {
+  while (ContextPC(context)) {
     const void* instruction_pointer =
-        reinterpret_cast<const void*>(context->Rip);
+        reinterpret_cast<const void*>(ContextPC(context));
     ScopedModuleHandle module;
     if (!frame_unwinder.TryUnwind(context, &module))
       return NATIVE_STACK_SAMPLER_TRY_UNWIND_FAILED;
@@ -355,8 +365,10 @@
       if (!::GetThreadContext(thread_handle, &thread_context))
         return NATIVE_STACK_SAMPLER_GET_THREAD_CONTEXT_FAILED;
 
-#if defined(_WIN64)
+#if defined(ARCH_CPU_X86_64)
       bottom = thread_context.Rsp;
+#elif defined(ARCH_CPU_ARM64)
+      bottom = thread_context.Sp;
 #else
       bottom = thread_context.Esp;
 #endif
diff --git a/base/profiler/win32_stack_frame_unwinder.cc b/base/profiler/win32_stack_frame_unwinder.cc
index b5db93b4..a6d1d45 100644
--- a/base/profiler/win32_stack_frame_unwinder.cc
+++ b/base/profiler/win32_stack_frame_unwinder.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/macros.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -121,7 +122,7 @@
   // TODO(chengx): update base::ModuleCache to return a ScopedModuleHandle and
   // use it for this module lookup.
   ScopedModuleHandle frame_module =
-      unwind_functions_->GetModuleForProgramCounter(context->Rip);
+      unwind_functions_->GetModuleForProgramCounter(ContextPC(context));
   if (!frame_module.IsValid()) {
     // There's no loaded module containing the instruction pointer. This can be
     // due to executing code that is not in a module. In particular,
@@ -142,20 +143,31 @@
   ULONG64 image_base;
   // Try to look up unwind metadata for the current function.
   PRUNTIME_FUNCTION runtime_function =
-      unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);
+      unwind_functions_->LookupFunctionEntry(ContextPC(context), &image_base);
 
   if (runtime_function) {
-    unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
-                                     context);
+    unwind_functions_->VirtualUnwind(image_base, ContextPC(context),
+                                     runtime_function, context);
     at_top_frame_ = false;
   } else {
     if (at_top_frame_) {
       at_top_frame_ = false;
 
       // This is a leaf function (i.e. a function that neither calls a function,
-      // nor allocates any stack space itself) so the return address is at RSP.
+      // nor allocates any stack space itself).
+#if defined(ARCH_CPU_X86_64)
+      // For X64, return address is at RSP.
       context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
       context->Rsp += 8;
+#elif defined(ARCH_CPU_ARM64)
+      // For leaf function on Windows ARM64, return address is at LR(X30).
+      // Add CONTEXT_UNWOUND_TO_CALL flag to avoid unwind ambiguity for tailcall
+      // on ARM64, because padding after tailcall is not guaranteed.
+      context->Pc = context->Lr;
+      context->ContextFlags |= CONTEXT_UNWOUND_TO_CALL;
+#else
+#error Unsupported Windows 64-bit Arch
+#endif
     } else {
       // In theory we shouldn't get here, as it means we've encountered a
       // function without unwind information below the top of the stack, which
diff --git a/base/profiler/win32_stack_frame_unwinder.h b/base/profiler/win32_stack_frame_unwinder.h
index c92d50cd2..144cc6e 100644
--- a/base/profiler/win32_stack_frame_unwinder.h
+++ b/base/profiler/win32_stack_frame_unwinder.h
@@ -12,6 +12,7 @@
 #include "base/base_export.h"
 #include "base/macros.h"
 #include "base/win/scoped_handle.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -25,6 +26,18 @@
 using PRUNTIME_FUNCTION = RUNTIME_FUNCTION*;
 #endif  // !defined(_WIN64)
 
+#if defined(ARCH_CPU_64_BITS)
+inline ULONG64 ContextPC(CONTEXT* context) {
+#if defined(ARCH_CPU_X86_64)
+  return context->Rip;
+#elif defined(ARCH_CPU_ARM64)
+  return context->Pc;
+#else
+#error Unsupported Windows 64-bit Arch
+#endif
+}
+#endif
+
 // Traits class to adapt GenericScopedHandle for HMODULES.
 class ModuleHandleTraits : public win::HandleTraits {
  public:
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 79e282d..498fcf8b 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2433,13 +2433,6 @@
           proguard_configs += [ "//build/android/multidex.flags" ]
         }
         proguard_mapping_path = _proguard_mapping_path
-        if (_use_chromium_linker) {
-          proguard_configs +=
-              [ "//base/android/proguard/explicit_jni_registration.flags" ]
-        } else {
-          proguard_configs +=
-              [ "//base/android/proguard/implicit_jni_registration.flags" ]
-        }
       }
 
       # Don't depend on the runtime_deps target in order to avoid having to
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index b9a7e0eb..66a80a8d 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-95394b4e05ae49a15270dca82cce5b3aab91ad7a
\ No newline at end of file
+089bba31a40b8d2ec9018064aeb2617bbf8ee985
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 04f615b4..71929b1 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-87a02c667580a1720ea4ee2c94ece6adad1e38cd
\ No newline at end of file
+ea1cb7ce191f61d3351efe3e141233d454b96db0
\ No newline at end of file
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 38d52361..815d857 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -978,6 +978,8 @@
 #      symbols required for lazy JNI registration.
 #    enable_compressed_relocations: Optional. If true, enable compressed
 #      ELF relocations to reduce native library size.
+#    use_gnu_hash_style: If true, the final library will include only GNU hash
+#      tables, rather than both SysV and GNU types (the default).
 template("chrome_common_shared_library") {
   shared_library(target_name) {
     forward_variables_from(invoker,
@@ -985,6 +987,7 @@
                            [
                              "enable_compressed_relocations",
                              "export_java_symbols",
+                             "use_gnu_hash_style",
                            ])
     _export_java_symbols =
         defined(invoker.export_java_symbols) && invoker.export_java_symbols
@@ -1018,6 +1021,20 @@
         invoker.enable_compressed_relocations) {
       configs += [ "//build/config/android:lld_pack_relocations" ]
     }
+
+    if (invoker.use_gnu_hash_style && target_cpu != "mipsel" &&
+        target_cpu != "mips64el") {
+      # By default, the static linker will create ELF executables with both
+      # SysV and GNU hash tables. Now that the chromium linker supports the GNU
+      # format, which is considerably smaller, ensure that the SysV one is
+      # never compiled in the final library (http://crbug.com/742525#c28). GNU
+      # hash support was added in Android M. Also not supported on MIPS
+      # architecture (http://crbug.com/811306).
+      if (!defined(ldflags)) {
+        ldflags = []
+      }
+      ldflags += [ "-Wl,--hash-style=gnu" ]
+    }
   }
 }
 
@@ -1034,20 +1051,8 @@
       ldflags = [ "-Wl,--long-plt" ]
     }
 
-    if (chromium_linker_supported && target_cpu != "mipsel" &&
-        target_cpu != "mipsel64") {
-      # By default, the static linker will create ELF executables with both
-      # SysV and GNU hash tables. Now that the chromium linker supports the
-      # GNU format, which is considerably smaller, ensure that the SysV one
-      # is never compiled in the final library. Note that MIPS platforms do
-      # not support the GNU format though.
-      if (!defined(ldflags)) {
-        ldflags = []
-      }
-      ldflags += [ "-Wl,--hash-style=gnu" ]
-    }
-
     enable_compressed_relocations = chromium_linker_supported && use_lld
+    use_gnu_hash_style = chromium_linker_supported
   }
 }
 
@@ -1315,14 +1320,7 @@
 
     export_java_symbols = true
     enable_compressed_relocations = use_lld
-
-    if (target_cpu != "mipsel" && target_cpu != "mips64el") {
-      # .gnu.hash support added in Android M. Without this flag, library will also
-      # contain an unused .hash section. (http://crbug.com/742525#c28)
-      # Not to be used for MIPS arhitecture since MIPS does not
-      # support hashstyle gnu: http://crbug.com/811306
-      ldflags = [ "-Wl,--hash-style=gnu" ]
-    }
+    use_gnu_hash_style = true
   }
 } else {
   group("monochrome_secondary_abi_lib") {
diff --git a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
index e337157..70e4b116 100644
--- a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
@@ -3,7 +3,7 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<org.chromium.chrome.browser.toolbar.ScrollingBottomViewResourceFrameLayout
+<org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
@@ -37,14 +37,14 @@
             android:paddingStart="@dimen/bottom_toolbar_start_padding"
             android:paddingEnd="@dimen/bottom_toolbar_end_padding" >
 
-            <org.chromium.chrome.browser.toolbar.HomeButton
+            <org.chromium.chrome.browser.toolbar.bottom.HomeButton
                 android:id="@+id/home_button"
                 style="@style/BottomToolbarButton"
                 android:contentDescription="@string/accessibility_toolbar_btn_home" />
 
             <include layout="@layout/toolbar_space" />
 
-            <org.chromium.chrome.browser.toolbar.ShareButton
+            <org.chromium.chrome.browser.toolbar.bottom.ShareButton
                 android:id="@+id/share_button"
                 style="@style/BottomToolbarButton"
                 android:src="@drawable/ic_share_white_24dp"
@@ -53,7 +53,7 @@
 
             <include layout="@layout/toolbar_space" />
 
-            <org.chromium.chrome.browser.toolbar.SearchAccelerator
+            <org.chromium.chrome.browser.toolbar.bottom.SearchAccelerator
                 android:id="@+id/search_accelerator"
                 android:layout_width="@dimen/search_accelerator_width"
                 android:layout_height="@dimen/search_accelerator_height"
@@ -78,4 +78,4 @@
 
     </org.chromium.chrome.browser.widget.bottomsheet.TouchRestrictingFrameLayout>
 
-</org.chromium.chrome.browser.toolbar.ScrollingBottomViewResourceFrameLayout>
+</org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout>
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_checkbox_bg.xml b/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_checkbox_bg.xml
new file mode 100644
index 0000000..08605a2
--- /dev/null
+++ b/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_checkbox_bg.xml
@@ -0,0 +1,11 @@
+<!-- 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. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <corners
+      android:radius="4dp" />
+  <solid
+      android:color="@color/autofill_assistant_light_blue" />
+</shape>
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml b/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml
index bca6084..95452f09 100644
--- a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml
+++ b/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml
@@ -34,13 +34,38 @@
 
     </org.chromium.chrome.browser.widget.FadingEdgeScrollView>
 
-    <CheckBox
-        android:id="@+id/terms_checkbox"
-        android:layout_width="match_parent"
+    <!--
+    Padding on the checkbox does not affect the padding to the left of the
+    checkbox field, so we wrap it in a FrameLayout and control the margins and
+    padding there.
+    -->
+    <FrameLayout
         android:layout_height="wrap_content"
-        android:layout_marginStart="5dp"
-        android:padding="2dp"
-        android:text="@string/autofill_assistant_terms" />
+        android:layout_width="match_parent"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginTop="28dp"
+        android:layout_marginBottom="20dp"
+        android:paddingTop="9dp"
+        android:paddingBottom="9dp"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:background="@drawable/autofill_assistant_checkbox_bg" >
+
+        <!--
+        TODO(crbug.com/806868): Remove the tint and text color to be compliant
+        with Chrome style.
+        -->
+        <android.support.v7.widget.AppCompatCheckBox
+            android:id="@+id/terms_checkbox"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="0dp"
+            android:padding="0dp"
+            android:paddingStart="12dp"
+            android:text="@string/autofill_assistant_terms" />
+
+    </FrameLayout>
 
     <include layout="@layout/autofill_assistant_payment_request_bottom_bar" />
 
diff --git a/chrome/android/java/res_autofill_assistant/values-v17/colors.xml b/chrome/android/java/res_autofill_assistant/values-v17/colors.xml
new file mode 100644
index 0000000..3d758fd
--- /dev/null
+++ b/chrome/android/java/res_autofill_assistant/values-v17/colors.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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. -->
+
+<resources>
+    <!--
+    TODO(crbuc.com/806868): Use a Chrome approved color and remove this.
+
+    Please see src/ui/android/java/res/values/colors.xml for the shared common colors.
+    -->
+    <color name="autofill_assistant_light_blue">#e9f0fd</color>
+</resources>
diff --git a/chrome/android/java/res_autofill_assistant/values-v17/styles.xml b/chrome/android/java/res_autofill_assistant/values-v17/styles.xml
index 25b517b..862d31a 100644
--- a/chrome/android/java/res_autofill_assistant/values-v17/styles.xml
+++ b/chrome/android/java/res_autofill_assistant/values-v17/styles.xml
@@ -23,7 +23,8 @@
     <style
         name="TextAppearance.AutofillAssistantFilledButton"
         parent="RobotoMediumStyle">
-         <item name="android:textColor">@color/modern_grey_50</item>
+        <item name="android:textColor">@color/modern_grey_50</item>
         <item name="android:textSize">@dimen/text_size_medium</item>
     </style>
+
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestUI.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestUI.java
index f8aecce..704412f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestUI.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/ui/PaymentRequestUI.java
@@ -20,6 +20,7 @@
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.text.method.LinkMovementMethod;
+import android.text.style.StyleSpan;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -319,6 +320,10 @@
         // Terms and services accepted checkbox. The state is passively propagated along to the
         // client when the pay/continue button is clicked.
         mTermsCheckBox = (CheckBox) mRequestView.findViewById(R.id.terms_checkbox);
+        StyleSpan boldSpan = new StyleSpan(android.graphics.Typeface.BOLD);
+        String termsString = context.getString(R.string.autofill_assistant_terms, origin);
+        mTermsCheckBox.setText(
+                SpanApplier.applySpans(termsString, new SpanInfo("<b>", "</b>", boldSpan)));
 
         // Create all the possible sections.
         mSectionSeparators = new ArrayList<>();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 7b970a1..58a761e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -301,14 +301,7 @@
         mShowingFullscreen = isInFullscreen;
 
         if (mSystemUiFullscreenResizeRunnable == null) {
-            mSystemUiFullscreenResizeRunnable = () -> {
-                View contentView = getContentView();
-                if (contentView != null) {
-                    Point viewportSize = getViewportSize();
-                    setSize(getWebContents(), contentView, viewportSize.x, viewportSize.y);
-                }
-                onViewportChanged();
-            };
+            mSystemUiFullscreenResizeRunnable = this::handleWindowInsetChanged;
         } else {
             getHandler().removeCallbacks(mSystemUiFullscreenResizeRunnable);
         }
@@ -367,13 +360,25 @@
         mInsetObserverView = view;
         if (mInsetObserverView != null) {
             mInsetObserverView.addObserver(this);
-            onViewportChanged();
+            handleWindowInsetChanged();
         }
     }
 
     @Override
     public void onInsetChanged(int left, int top, int right, int bottom) {
-        if (mShowingFullscreen) onViewportChanged();
+        if (mShowingFullscreen) handleWindowInsetChanged();
+    }
+
+    private void handleWindowInsetChanged() {
+        // Notify the WebContents that the size has changed.
+        View contentView = getContentView();
+        if (contentView != null) {
+            Point viewportSize = getViewportSize();
+            setSize(getWebContents(), contentView, viewportSize.x, viewportSize.y);
+        }
+        // Notify the compositor layout that the size has changed.  The layout does not drive
+        // the WebContents sizing, so this needs to be done in addition to the above size update.
+        onViewportChanged();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnable.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnable.java
index c3e06cf2e..6dc3c0e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnable.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnable.java
@@ -5,10 +5,10 @@
 package org.chromium.chrome.browser.crash;
 
 import android.os.Build;
-import android.util.Patterns;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
+import org.chromium.base.PiiElider;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.components.minidump_uploader.CrashFileManager;
 
@@ -20,8 +20,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Extracts the recent logcat output from an Android device, elides PII sensitive info from it,
@@ -37,43 +35,6 @@
 
     protected static final int LOGCAT_SIZE = 256; // Number of lines.
 
-    protected static final String EMAIL_ELISION = "XXX@EMAIL.ELIDED";
-
-    @VisibleForTesting
-    protected static final String URL_ELISION = "HTTP://WEBADDRESS.ELIDED";
-
-    private static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
-
-    private static final Pattern IP_ADDRESS = Pattern.compile(
-            "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
-            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
-            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
-            + "|[1-9][0-9]|[0-9]))");
-
-    private static final String IRI =
-            "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
-
-    private static final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
-    private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
-    private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
-
-    private static final Pattern DOMAIN_NAME =
-            Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
-
-    private static final Pattern LIKELY_EXCEPTION_LOG =
-            Pattern.compile("\\sat\\sorg\\.chromium\\.[^ ]+.");
-
-    private static final Pattern WEB_URL =
-            Pattern.compile("(?:\\b|^)((?:(http|https|Http|Https|rtsp|Rtsp):"
-                    + "\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
-                    + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
-                    + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
-                    + "(?:" + DOMAIN_NAME + ")"
-                    + "(?:\\:\\d{1,5})?)"
-                    + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~"
-                    + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
-                    + "(?:\\b|$)");
-
     @VisibleForTesting
     protected static final String BEGIN_MICRODUMP = "-----BEGIN BREAKPAD MICRODUMP-----";
     @VisibleForTesting
@@ -82,36 +43,6 @@
     protected static final String SNIPPED_MICRODUMP =
             "-----SNIPPED OUT BREAKPAD MICRODUMP FOR THIS CRASH-----";
 
-    @VisibleForTesting
-    protected static final String IP_ELISION = "1.2.3.4";
-
-    @VisibleForTesting
-    protected static final String MAC_ELISION = "01:23:45:67:89:AB";
-
-    @VisibleForTesting
-    protected static final String CONSOLE_ELISION = "[ELIDED:CONSOLE(0)] ELIDED CONSOLE MESSAGE";
-
-    private static final Pattern MAC_ADDRESS =
-            Pattern.compile("([0-9a-fA-F]{2}[-:]+){5}[0-9a-fA-F]{2}");
-
-    private static final Pattern CONSOLE_MSG = Pattern.compile("\\[\\w*:CONSOLE.*\\].*");
-
-    private static final String[] CHROME_NAMESPACE = new String[] {"org.chromium.", "com.google."};
-
-    private static final String[] SYSTEM_NAMESPACE = new String[] {"android.accessibilityservice",
-            "android.accounts", "android.animation", "android.annotation", "android.app",
-            "android.appwidget", "android.bluetooth", "android.content", "android.database",
-            "android.databinding", "android.drm", "android.gesture", "android.graphics",
-            "android.hardware", "android.inputmethodservice", "android.location", "android.media",
-            "android.mtp", "android.net", "android.nfc", "android.opengl", "android.os",
-            "android.preference", "android.print", "android.printservice", "android.provider",
-            "android.renderscript", "android.sax", "android.security", "android.service",
-            "android.speech", "android.support", "android.system", "android.telecom",
-            "android.telephony", "android.test", "android.text", "android.transition",
-            "android.util", "android.view", "android.webkit", "android.widget", "com.android.",
-            "dalvik.", "java.", "javax.", "org.apache.", "org.json.", "org.w3c.dom.", "org.xml.",
-            "org.xmlpull."};
-
     private final File mMinidumpFile;
 
     /**
@@ -254,107 +185,13 @@
     protected static List<String> elideLogcat(List<String> rawLogcat) {
         List<String> elided = new ArrayList<String>(rawLogcat.size());
         for (String ln : rawLogcat) {
-            ln = elideEmail(ln);
-            ln = elideUrl(ln);
-            ln = elideIp(ln);
-            ln = elideMac(ln);
-            ln = elideConsole(ln);
+            ln = PiiElider.elideEmail(ln);
+            ln = PiiElider.elideUrl(ln);
+            ln = PiiElider.elideIp(ln);
+            ln = PiiElider.elideMac(ln);
+            ln = PiiElider.elideConsole(ln);
             elided.add(ln);
         }
         return elided;
     }
-
-    /**
-     * Elides any emails in the specified {@link String} with
-     * {@link #EMAIL_ELISION}.
-     *
-     * @param original String potentially containing emails.
-     * @return String with elided emails.
-     */
-    @VisibleForTesting
-    protected static String elideEmail(String original) {
-        return Patterns.EMAIL_ADDRESS.matcher(original).replaceAll(EMAIL_ELISION);
-    }
-
-    /**
-     * Elides any URLs in the specified {@link String} with
-     * {@link #URL_ELISION}.
-     *
-     * @param original String potentially containing URLs.
-     * @return String with elided URLs.
-     */
-    @VisibleForTesting
-    protected static String elideUrl(String original) {
-        // Url-matching is fussy. If something looks like an exception message, just return.
-        if (LIKELY_EXCEPTION_LOG.matcher(original).find()) return original;
-        StringBuilder buffer = new StringBuilder(original);
-        Matcher matcher = WEB_URL.matcher(buffer);
-        int start = 0;
-        while (matcher.find(start)) {
-            start = matcher.start();
-            int end = matcher.end();
-            String url = buffer.substring(start, end);
-            if (!likelyToBeChromeNamespace(url) && !likelyToBeSystemNamespace(url)) {
-                buffer.replace(start, end, URL_ELISION);
-                end = start + URL_ELISION.length();
-                matcher = WEB_URL.matcher(buffer);
-            }
-            start = end;
-        }
-        return buffer.toString();
-    }
-
-    public static boolean likelyToBeChromeNamespace(String url) {
-        for (String ns : CHROME_NAMESPACE) {
-            if (url.startsWith(ns)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean likelyToBeSystemNamespace(String url) {
-        for (String ns : SYSTEM_NAMESPACE) {
-            if (url.startsWith(ns)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Elides any IP addresses in the specified {@link String} with
-     * {@link #IP_ELISION}.
-     *
-     * @param original String potentially containing IPs.
-     * @return String with elided IPs.
-     */
-    @VisibleForTesting
-    protected static String elideIp(String original) {
-        return Patterns.IP_ADDRESS.matcher(original).replaceAll(IP_ELISION);
-    }
-
-    /**
-     * Elides any MAC addresses in the specified {@link String} with
-     * {@link #MAC_ELISION}.
-     *
-     * @param original String potentially containing MACs.
-     * @return String with elided MACs.
-     */
-    @VisibleForTesting
-    protected static String elideMac(String original) {
-        return MAC_ADDRESS.matcher(original).replaceAll(MAC_ELISION);
-    }
-
-    /**
-     * Elides any console messages in the specified {@link String} with
-     * {@link #CONSOLE_ELISION}.
-     *
-     * @param original String potentially containing console messages.
-     * @return String with elided console messages.
-     */
-    @VisibleForTesting
-    protected static String elideConsole(String original) {
-        return CONSOLE_MSG.matcher(original).replaceAll(CONSOLE_ELISION);
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
index ee45085d..87b0e07a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/PureJavaExceptionReporter.java
@@ -15,6 +15,7 @@
 import org.chromium.base.BuildConfig;
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.PiiElider;
 import org.chromium.base.StrictModeContext;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.MainDex;
@@ -137,7 +138,8 @@
         addPairedString(GMS_CORE_VERSION, buildInfo.gmsVersionCode);
         addPairedString(INSTALLER_PACKAGE_NAME, buildInfo.installerPackageName);
         addPairedString(ABI_NAME, buildInfo.abiString);
-        addPairedString(EXCEPTION_INFO, Log.getStackTraceString(javaException));
+        addPairedString(EXCEPTION_INFO,
+                PiiElider.sanitizeStacktrace(Log.getStackTraceString(javaException)));
         addPairedString(EARLY_JAVA_EXCEPTION, "true");
         addPairedString(PACKAGE,
                 String.format("%s v%s (%s)", BuildConfig.FIREBASE_APP_ID, buildInfo.versionCode,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
index 7001932a..a2bb725 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/MenuButton.java
@@ -48,7 +48,7 @@
      * @param onTouchListener An {@link OnTouchListener} that is triggered when the menu button is
      *                        clicked.
      */
-    void setTouchListener(OnTouchListener onTouchListener) {
+    public void setTouchListener(OnTouchListener onTouchListener) {
         mMenuImageButton.setOnTouchListener(onTouchListener);
     }
 
@@ -113,7 +113,7 @@
     /**
      * @return Whether the update badge is showing.
      */
-    boolean isShowingAppMenuUpdateBadge() {
+    public boolean isShowingAppMenuUpdateBadge() {
         return mUpdateBadgeView.getVisibility() == View.VISIBLE;
     }
 
@@ -149,7 +149,7 @@
         return mMenuImageButton;
     }
 
-    void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
+    public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
         mThemeColorProvider = themeColorProvider;
         mThemeColorProvider.addObserver(this);
     }
@@ -160,7 +160,7 @@
         setUseLightDrawables(ColorUtils.shouldUseLightForegroundOnBackground(primaryColor));
     }
 
-    void destroy() {
+    public void destroy() {
         if (mThemeColorProvider != null) {
             mThemeColorProvider.removeObserver(this);
             mThemeColorProvider = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
index 0fbc2514..ddd50c61 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
@@ -15,9 +15,7 @@
 
 import java.util.List;
 
-/**
- * Provides the number of open regular tabs for display in the tab switcher icon.
- */
+/** A provider that notifies its observers when the number of tabs changes. */
 public class TabCountProvider {
     /** An observer that is notified of changes to the number of open tabs. */
     public interface TabCountObserver {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index b2e83c23..18f8162 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -88,6 +88,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
+import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarCoordinator;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController.ActionBarDelegate;
 import org.chromium.chrome.browser.toolbar.top.Toolbar;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarCoordinator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
index cf1bac1..bc65fc4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -16,6 +16,9 @@
 import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
+import org.chromium.chrome.browser.toolbar.MenuButton;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.resources.ResourceManager;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
similarity index 95%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarCoordinator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
index 27ef91c32..0f79804 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -19,7 +19,11 @@
 import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.toolbar.BrowsingModeBottomToolbarViewBinder.ViewHolder;
+import org.chromium.chrome.browser.toolbar.MenuButton;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
+import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
+import org.chromium.chrome.browser.toolbar.bottom.BrowsingModeBottomToolbarViewBinder.ViewHolder;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.resources.ResourceManager;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarMediator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
index 748ec83d..103528b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.widget.textbubble.TextBubble;
 import org.chromium.components.feature_engagement.FeatureConstants;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarModel.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java
index 4b33b4b6..d587f84 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarViewBinder.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java
index 17cc1fa..5aa1ace 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/HomeButton.java
similarity index 93%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/HomeButton.java
index 59c7c9ed..fb84ab90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/HomeButton.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -11,6 +11,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.widget.ChromeImageButton;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ScrollingBottomViewResourceFrameLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/ScrollingBottomViewResourceFrameLayout.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
index 0aeac71..0200eb7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ScrollingBottomViewResourceFrameLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.Context;
 import android.graphics.Canvas;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/SearchAccelerator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
similarity index 94%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/SearchAccelerator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java
index 03ac1943..7fdcc90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/SearchAccelerator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -13,6 +13,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.ui.widget.ChromeImageButton;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ShareButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/ShareButton.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java
index 638164f..9c7fc0ce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ShareButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 import org.chromium.ui.widget.ChromeImageButton;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
similarity index 93%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarCoordinator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
index ecb5d21..4db47f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -15,6 +15,11 @@
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
+import org.chromium.chrome.browser.toolbar.IncognitoToggleTabLayout;
+import org.chromium.chrome.browser.toolbar.MenuButton;
+import org.chromium.chrome.browser.toolbar.NewTabButton;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
 
 /**
  * The coordinator for the tab switcher mode bottom toolbar. This class handles all interactions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarMediator.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
index 9457f9d..49e9359 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java
@@ -2,12 +2,13 @@
 // 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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.content.res.ColorStateList;
 
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
+import org.chromium.chrome.browser.toolbar.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ThemeColorProvider.ThemeColorObserver;
 
 /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.java
similarity index 94%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarModel.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.java
index 010399bd7..7897f53 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import org.chromium.chrome.browser.modelutil.PropertyModel;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarViewBinder.java
rename to chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java
index aacfe6d5..cf5afeb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.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.toolbar;
+package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
 
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 4eb1cbc9..fc6e61d 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1565,27 +1565,14 @@
   "java/src/org/chromium/chrome/browser/tabmodel/document/StorageDelegate.java",
   "java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java",
   "java/src/org/chromium/chrome/browser/tasks/TasksUma.java",
-  "java/src/org/chromium/chrome/browser/toolbar/BottomToolbarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarMediator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarModel.java",
-  "java/src/org/chromium/chrome/browser/toolbar/BrowsingModeBottomToolbarViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/HomePageButton.java",
-  "java/src/org/chromium/chrome/browser/toolbar/HomeButton.java",
   "java/src/org/chromium/chrome/browser/toolbar/IncognitoStateProvider.java",
   "java/src/org/chromium/chrome/browser/toolbar/IncognitoToggleTabLayout.java",
   "java/src/org/chromium/chrome/browser/toolbar/KeyboardNavigationListener.java",
   "java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java",
   "java/src/org/chromium/chrome/browser/toolbar/MenuButton.java",
   "java/src/org/chromium/chrome/browser/toolbar/NewTabButton.java",
-  "java/src/org/chromium/chrome/browser/toolbar/ScrollingBottomViewResourceFrameLayout.java",
-  "java/src/org/chromium/chrome/browser/toolbar/SearchAccelerator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/ShareButton.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java",
-  "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarMediator.java",
-  "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarModel.java",
-  "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherBottomToolbarViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonCoordinator.java",
   "java/src/org/chromium/chrome/browser/toolbar/TabSwitcherButtonProperties.java",
@@ -1596,6 +1583,19 @@
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarTabController.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/HomeButton.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/SearchAccelerator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/ShareButton.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarMediator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarModel.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
index d48e57c34..4cbb6529 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
@@ -339,7 +339,7 @@
         Assert.assertTrue("Page did not enter fullscreen",
                 DOMUtils.isFullscreen(mVrBrowserTestFramework.getFirstTabWebContents()));
 
-        mController.pressReleaseAppButton();
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         CriteriaHelper.pollInstrumentationThread(
                 ()
                         -> {
@@ -406,10 +406,11 @@
         // is not actually visible, we'll hit a DCHECK in the native code.
         NativeUiUtils.clickElementAndWaitForUiQuiescence(
                 UserFriendlyElementName.OMNIBOX_TEXT_FIELD, new PointF());
-        NativeUiUtils.revertToRealInput();
         // Wait for the URL bar to re-appear, which we take as a signal that we've exited omnibox
         // text input mode.
-        NativeUiUtils.performActionAndWaitForVisibilityStatus(UserFriendlyElementName.URL,
-                true /* visible */, () -> { mController.pressReleaseAppButton(); });
+        NativeUiUtils.performActionAndWaitForVisibilityStatus(
+                UserFriendlyElementName.URL, true /* visible */, () -> {
+                    NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
+                });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
index 6f0651be..7c91c0b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
@@ -15,6 +15,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.PointF;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
@@ -316,7 +317,6 @@
     private void reEntryFromVrBrowserImpl(String url, WebXrVrTestFramework framework)
             throws InterruptedException {
         VrBrowserTransitionUtils.forceEnterVrBrowserOrFail(POLL_TIMEOUT_LONG_MS);
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
 
         framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
         framework.enterSessionWithUserGestureOrFail();
@@ -324,7 +324,7 @@
         framework.executeStepAndWait("stepVerifyFirstPresent()");
         // The bug did not reproduce with vrDisplay.exitPresent(), so it might not reproduce with
         // session.end(). Instead, use the controller to exit.
-        controller.pressReleaseAppButton();
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         framework.executeStepAndWait("stepVerifyMagicWindow()");
 
         framework.enterSessionWithUserGestureOrFail();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserWebInputEditingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserWebInputEditingTest.java
index 7fad88ac..ce67975 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserWebInputEditingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserWebInputEditingTest.java
@@ -404,12 +404,7 @@
                 "Keyboard did not show from focusing a web input box", POLL_TIMEOUT_SHORT_MS,
                 POLL_CHECK_INTERVAL_SHORT_MS);
 
-        NativeUiUtils.revertToRealInput();
-        EmulatedVrController controller = new EmulatedVrController(mVrTestRule.getActivity());
-        // Do this several times to help combat the flakiness of the emulated controller.
-        for (int i = 0; i < 3; ++i) {
-            controller.pressReleaseAppButton();
-        }
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         CriteriaHelper.pollInstrumentationThread(
                 ()
                         -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
index 2f7a566a..d3e5f4f9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
@@ -300,7 +300,6 @@
             @CommandLineFlags.Add({"enable-features=WebXR"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testScreenTapsRegisteredOnCardboard_WebXr() throws InterruptedException {
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
         mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
                 WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_input"),
                 PAGE_LOAD_TIMEOUT_S);
@@ -397,8 +396,7 @@
             throws InterruptedException {
         framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
         framework.enterSessionWithUserGestureOrFail();
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
-        controller.pressReleaseAppButton();
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         assertAppButtonEffect(true /* shouldHaveExited */, framework);
         framework.assertNoJavaScriptErrors();
     }
@@ -477,8 +475,7 @@
         MockVrDaydreamApi mockApi = new MockVrDaydreamApi();
         VrShellDelegateUtils.getDelegateInstance().overrideDaydreamApiForTesting(mockApi);
 
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
-        controller.pressReleaseAppButton();
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         Assert.assertFalse("App button left Chrome",
                 ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
                     @Override
@@ -557,8 +554,7 @@
         framework.enterSessionWithUserGestureOrFail();
         // Wait for page to stop submitting frames.
         framework.waitOnJavaScriptStep();
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
-        controller.pressReleaseAppButton();
+        NativeUiUtils.clickAppButton(UserFriendlyElementName.NONE, new PointF());
         assertAppButtonEffect(true /* shouldHaveExited */, framework);
         framework.assertNoJavaScriptErrors();
     }
@@ -742,7 +738,6 @@
     private void testAppButtonLongPressDisplaysPermissionsImpl() throws InterruptedException {
         // Note that we need to pass in the WebContents to use throughout this because automatically
         // using the first tab's WebContents doesn't work in Incognito.
-        EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
         EmbeddedTestServer server = mTestRule.getTestServer();
         boolean teardownServer = false;
         if (server == null) {
@@ -767,12 +762,13 @@
         NativeUiUtils.performActionAndWaitForVisibilityStatus(
                 UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, false /* visible */, () -> {});
         NativeUiUtils.performActionAndWaitForVisibilityStatus(
-                UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */,
-                () -> { controller.sendAppButtonToggleEvent(); });
+                UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {
+                    NativeUiUtils.pressAppButton(UserFriendlyElementName.NONE, new PointF());
+                });
         // The toast should automatically disappear after ~5 second after the button is pressed,
         // regardless of whether it's released or not.
         SystemClock.sleep(1000);
-        controller.sendAppButtonToggleEvent();
+        NativeUiUtils.releaseAppButton(UserFriendlyElementName.NONE, new PointF());
         SystemClock.sleep(3500);
         // Make sure it's still present shortly before we expect it to disappear.
         NativeUiUtils.performActionAndWaitForVisibilityStatus(
@@ -781,8 +777,9 @@
                 UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, false /* visible */, () -> {});
         // Do the same, but make sure the toast disappears even with the button still held.
         NativeUiUtils.performActionAndWaitForVisibilityStatus(
-                UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */,
-                () -> { controller.sendAppButtonToggleEvent(); });
+                UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {
+                    NativeUiUtils.pressAppButton(UserFriendlyElementName.NONE, new PointF());
+                });
         SystemClock.sleep(4500);
         NativeUiUtils.performActionAndWaitForVisibilityStatus(
                 UserFriendlyElementName.WEB_XR_AUDIO_INDICATOR, true /* visible */, () -> {});
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
index e2318f5..f3603af 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
@@ -82,7 +82,44 @@
      */
     public static void clickElement(int elementName, PointF position) {
         TestVrShellDelegate.getInstance().performControllerActionForTesting(
-                elementName, VrControllerTestAction.CLICK, position);
+                elementName, VrControllerTestAction.CLICK_DOWN, position);
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.CLICK_UP, position);
+    }
+
+    /**
+     * Clicks the app button while pointed at a UI element.
+     * @param elementName The UserFriendlyElementName that will be pointed at.
+     * @param position A PointF specifying where on the element to point at relative to a unit
+     *        square centered on (0, 0).
+     */
+    public static void clickAppButton(int elementName, PointF position) {
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.APP_DOWN, position);
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.APP_UP, position);
+    }
+
+    /**
+     * Presses the app button down while pointed at a UI element.
+     * @param elementName The UserFriendlyElementName that will be pointed at.
+     * @param position A PointF specifying where on the element to point at relative to a unit
+     *        square centered on (0, 0).
+     */
+    public static void pressAppButton(int elementName, PointF position) {
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.APP_DOWN, position);
+    }
+
+    /**
+     * Releases the app button while pointed at a UI element.
+     * @param elementName The UserFriendlyElementName that will be pointed at.
+     * @param position A PointF specifying where on the element to point at relative to a unit
+     *        square centered on (0, 0).
+     */
+    public static void releaseAppButton(int elementName, PointF position) {
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.APP_UP, position);
     }
 
     /**
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java
index e2c777e..68d478a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/crash/LogcatExtractionRunnableUnitTest.java
@@ -30,109 +30,6 @@
     private static final int MAX_LINES = 5;
 
     @Test
-    public void testElideEmail() {
-        String original = "email me at someguy@mailservice.com";
-        String expected = "email me at XXX@EMAIL.ELIDED";
-        assertEquals(expected, LogcatExtractionRunnable.elideEmail(original));
-    }
-
-    @Test
-    public void testElideUrl() {
-        String original = "file bugs at crbug.com";
-        String expected = "file bugs at HTTP://WEBADDRESS.ELIDED";
-        assertEquals(expected, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl2() {
-        String original =
-                "exception at org.chromium.chrome.browser.crash.LogcatExtractionRunnableUnitTest";
-        assertEquals(original, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl3() {
-        String original = "file bugs at crbug.com or code.google.com";
-        String expected = "file bugs at HTTP://WEBADDRESS.ELIDED or HTTP://WEBADDRESS.ELIDED";
-        assertEquals(expected, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl4() {
-        String original = "test shorturl.com !!!";
-        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
-        assertEquals(expected, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl5() {
-        String original = "test just.the.perfect.len.url !!!";
-        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
-        assertEquals(expected, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl6() {
-        String original = "test a.very.very.very.very.very.long.url !!!";
-        String expected = "test HTTP://WEBADDRESS.ELIDED !!!";
-        assertEquals(expected, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl7() {
-        String original = " at android.content.Intent \n at java.util.ArrayList";
-        assertEquals(original, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl8() {
-        String original = "exception at org.chromium.chrome.browser.compositor.scene_layer."
-                + "TabListSceneLayer.nativeUpdateLayer(Native Method)";
-        assertEquals(original, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideUrl9() {
-        String original = "I/dalvikvm( 5083): at org.chromium.chrome.browser.compositor."
-                + "scene_layer.TabListSceneLayer.nativeUpdateLayer(Native Method)";
-        assertEquals(original, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testDontElideFileSuffixes() {
-        String original = "chromium_android_linker.so";
-        assertEquals(original, LogcatExtractionRunnable.elideUrl(original));
-    }
-
-    @Test
-    public void testElideIp() {
-        String original = "traceroute 127.0.0.1";
-        String expected = "traceroute 1.2.3.4";
-        assertEquals(expected, LogcatExtractionRunnable.elideIp(original));
-    }
-
-    @Test
-    public void testElideMac1() {
-        String original = "MAC: AB-AB-AB-AB-AB-AB";
-        String expected = "MAC: 01:23:45:67:89:AB";
-        assertEquals(expected, LogcatExtractionRunnable.elideMac(original));
-    }
-
-    @Test
-    public void testElideMac2() {
-        String original = "MAC: AB:AB:AB:AB:AB:AB";
-        String expected = "MAC: 01:23:45:67:89:AB";
-        assertEquals(expected, LogcatExtractionRunnable.elideMac(original));
-    }
-
-    @Test
-    public void testElideConsole() {
-        String original = "I/chromium(123): [INFO:CONSOLE(2)] hello!";
-        String expected = "I/chromium(123): [ELIDED:CONSOLE(0)] ELIDED CONSOLE MESSAGE";
-        assertEquals(expected, LogcatExtractionRunnable.elideConsole(original));
-    }
-
-    @Test
     public void testLogTagNotElided() {
         List<String> original = Arrays.asList(new String[] {"I/cr_FooBar(123): Some message"});
         assertEquals(original, LogcatExtractionRunnable.elideLogcat(original));
diff --git a/chrome/android/static_initializers.gni b/chrome/android/static_initializers.gni
index 8adb769..c97f976 100644
--- a/chrome/android/static_initializers.gni
+++ b/chrome/android/static_initializers.gni
@@ -13,7 +13,7 @@
     (!is_debug && !using_sanitizer && proprietary_codecs)) {
   # Define expectations only for target_cpu covered by trybots.
   if (target_cpu == "arm") {
-    expected_static_initializer_count = 5
+    expected_static_initializer_count = 4
   } else if (target_cpu == "arm64") {
     expected_static_initializer_count = 3
   }
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index d4800ed..6e92c5d 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -680,15 +680,6 @@
         </message>
       </if>
 
-      <if expr="toolkit_views">
-        <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION" desc="Text of the reason to sign out">
-          Authentication certificate failed. Please sign in to Chromium again as <ph name="USER_NAME">$1<ex>pat@example.com</ex></ph> or contact your administrator for more information. <ph name="ADDITIONAL_EXPLANATION">$2</ph>
-        </message>
-        <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION_WITHOUT_USER_NAME" desc="Text of the reason to sign out when there is no valid email address">
-          Authentication certificate failed. Please sign in to Chromium again or contact your administrator for more information. <ph name="ADDITIONAL_EXPLANATION">$2</ph>
-        </message>
-      </if>
-
       <!-- Signin Email Confirmation tab modal dialog -->
       <if expr="not chromeos">
         <message name="IDS_SIGNIN_EMAIL_CONFIRMATION_TITLE" desc="Title of the signin email confirmation tab modal dialog.">
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 963b871c..d48b118 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6256,21 +6256,6 @@
         This account is managed by <ph name="DOMAIN">$1<ex>example.com</ex></ph>
       </message>
 
-      <!-- Enterprise force-sign-out dialog-->
-      <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_TITLE" desc="The title of the dialog shown when the user closes a browser and force sign in is enabled and local auth info is not valid.">
-        Sign in certification is not valid, window closing in <ph name="MINUTES">{0,number,00}<ex>5</ex></ph> : <ph name="SECONDS">{1,number,00}<ex>00</ex></ph>
-      </message>
-      <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_CLOSE_CONFIRM" desc="Text of the button to sign in again immediately.">
-        Sign in now
-      </message>
-      <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_CLOSE_DELAY" desc="Text of the button to sign in again later">
-        Cancel
-      </message>
-      <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_ADDITIONAL_EXPLANATION" desc="Text of auto close warning">
-        All browser windows will soon be closed automatically without sign in.
-      </message>
-
-
       <!-- Cookies Window -->
       <message name="IDS_COOKIES_REMOVE_LABEL" desc="The label of the 'Remove' button in the Cookies Window">
         Remove
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 42fcce87..783a064 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -691,15 +691,6 @@
         </message>
       </if>
 
-      <if expr="toolkit_views">
-        <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION" desc="Text of the reason to sign out">
-          Authentication certificate failed. Please sign in to Google Chrome again as <ph name="USER_NAME">$1<ex>pat@example.com</ex></ph> or contact your administrator for more information. <ph name="ADDITIONAL_EXPLANATION">$2</ph>
-        </message>
-        <message name="IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION_WITHOUT_USER_NAME" desc="Text of the reason to sign out when there is no valid email address">
-          Authentication certificate failed. Please sign in to Google Chrome again or contact your administrator for more information. <ph name="ADDITIONAL_EXPLANATION">$2</ph>
-      </message>
-      </if>
-
       <!-- Signin Email Confirmation tab modal dialog -->
       <if expr="not chromeos">
         <message name="IDS_SIGNIN_EMAIL_CONFIRMATION_TITLE" desc="Title of the signin email confirmation tab modal dialog.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 38158e06..bef2776b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4096,6 +4096,8 @@
 
   if (enable_offline_pages) {
     sources += [
+      "offline_pages/auto_fetch_page_load_watcher.cc",
+      "offline_pages/auto_fetch_page_load_watcher.h",
       "offline_pages/background_loader_offliner.cc",
       "offline_pages/background_loader_offliner.h",
       "offline_pages/download_archive_manager.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4360f405..e670ad2 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -384,12 +384,12 @@
      kAccountConsistencyFeatureMethodDiceFixAuthErrors}};
 
 const FeatureEntry::FeatureVariation kAccountConsistencyFeatureVariations[] = {
-    {"Dice", kAccountConsistencyDice, arraysize(kAccountConsistencyDice),
+    {"Dice", kAccountConsistencyDice, base::size(kAccountConsistencyDice),
      nullptr /* variation_id */},
     {"Dice (migration)", kAccountConsistencyDiceMigration,
-     arraysize(kAccountConsistencyDiceMigration), nullptr /* variation_id */},
+     base::size(kAccountConsistencyDiceMigration), nullptr /* variation_id */},
     {"Dice (fix auth errors)", kAccountConsistencyDiceFixAuthErrors,
-     arraysize(kAccountConsistencyDiceFixAuthErrors),
+     base::size(kAccountConsistencyDiceFixAuthErrors),
      nullptr /* variation_id */}};
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
@@ -789,6 +789,13 @@
      switches::kTLS13VariantFinal},
 };
 
+const FeatureEntry::FeatureParam kEnforceTLS13DowngradeKnownOnly[] = {
+    {"known_roots_only", "true"}};
+
+const FeatureEntry::FeatureVariation kEnforceTLS13DowngradeFeatureVariations[] =
+    {{"(Known Root Only)", kEnforceTLS13DowngradeKnownOnly,
+      base::size(kEnforceTLS13DowngradeKnownOnly), nullptr}};
+
 #if !defined(OS_ANDROID)
 const FeatureEntry::Choice kEnableAudioFocusChoices[] = {
     {flag_descriptions::kEnableAudioFocusDisabled, "", ""},
@@ -985,7 +992,7 @@
 
 const FeatureEntry::FeatureVariation kDataReductionMainMenuFeatureVariations[] =
     {{"(persistent)", kPersistentMenuItemEnabled,
-      arraysize(kPersistentMenuItemEnabled), nullptr}};
+      base::size(kPersistentMenuItemEnabled), nullptr}};
 #endif  // OS_ANDROID
 
 const FeatureEntry::FeatureParam kDetectingHeavyPagesLowThresholdEnabled[] = {
@@ -2031,10 +2038,9 @@
     {"translate-force-trigger-on-english",
      flag_descriptions::kTranslateForceTriggerOnEnglishName,
      flag_descriptions::kTranslateForceTriggerOnEnglishDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         language::kOverrideTranslateTriggerInIndia,
-         kTranslateForceTriggerOnEnglishVariations,
-         language::kOverrideTranslateTriggerInIndia.name)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(language::kOverrideTranslateTriggerInIndia,
+                                    kTranslateForceTriggerOnEnglishVariations,
+                                    "OverrideTranslateTriggerInIndia")},
     {"translate-explicit-ask",
      flag_descriptions::kTranslateExplicitLanguageAskName,
      flag_descriptions::kTranslateExplicitLanguageAskDescription, kOsAndroid,
@@ -2498,10 +2504,9 @@
          offline_pages::kOfflinePagesShowAlternateDinoPageFeature)},
     {"offline-indicator-choice", flag_descriptions::kOfflineIndicatorChoiceName,
      flag_descriptions::kOfflineIndicatorChoiceDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         offline_pages::kOfflineIndicatorFeature,
-         kOfflineIndicatorFeatureVariations,
-         offline_pages::kOfflineIndicatorFeature.name)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(offline_pages::kOfflineIndicatorFeature,
+                                    kOfflineIndicatorFeatureVariations,
+                                    "OfflineIndicator")},
     {"offline-indicator-always-http-probe",
      flag_descriptions::kOfflineIndicatorAlwaysHttpProbeName,
      flag_descriptions::kOfflineIndicatorAlwaysHttpProbeDescription, kOsAndroid,
@@ -2605,7 +2610,9 @@
      MULTI_VALUE_TYPE(kTLS13VariantChoices)},
     {"enforce-tls13-downgrade", flag_descriptions::kEnforceTLS13DowngradeName,
      flag_descriptions::kEnforceTLS13DowngradeDescription, kOsAll,
-     FEATURE_VALUE_TYPE(net::features::kEnforceTLS13Downgrade)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(net::features::kEnforceTLS13Downgrade,
+                                    kEnforceTLS13DowngradeFeatureVariations,
+                                    "EnforceTLS13Downgrade")},
     {"enable-scroll-anchor-serialization",
      flag_descriptions::kEnableScrollAnchorSerializationName,
      flag_descriptions::kEnableScrollAnchorSerializationDescription, kOsAll,
@@ -2631,7 +2638,7 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(
          ntp_snippets::kCategoryOrder,
          kContentSuggestionsCategoryOrderFeatureVariations,
-         ntp_snippets::kCategoryOrder.name)},
+         "ContentSuggestionsCategoryOrder")},
     {"content-suggestions-category-ranker",
      flag_descriptions::kContentSuggestionsCategoryRankerName,
      flag_descriptions::kContentSuggestionsCategoryRankerDescription,
@@ -2639,7 +2646,7 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(
          ntp_snippets::kCategoryRanker,
          kContentSuggestionsCategoryRankerFeatureVariations,
-         ntp_snippets::kCategoryRanker.name)},
+         "ContentSuggestionsCategoryRanker")},
     {"content-suggestions-debug-log",
      flag_descriptions::kContentSuggestionsDebugLogName,
      flag_descriptions::kContentSuggestionsDebugLogDescription, kOsAndroid,
@@ -2681,10 +2688,9 @@
     {"enable-ntp-remote-suggestions",
      flag_descriptions::kEnableNtpRemoteSuggestionsName,
      flag_descriptions::kEnableNtpRemoteSuggestionsDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         ntp_snippets::kArticleSuggestionsFeature,
-         kRemoteSuggestionsFeatureVariations,
-         ntp_snippets::kArticleSuggestionsFeature.name)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(ntp_snippets::kArticleSuggestionsFeature,
+                                    kRemoteSuggestionsFeatureVariations,
+                                    "NTPArticleSuggestions")},
     {"enable-ntp-asset-download-suggestions",
      flag_descriptions::kEnableNtpAssetDownloadSuggestionsName,
      flag_descriptions::kEnableNtpAssetDownloadSuggestionsDescription,
@@ -2706,7 +2712,7 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(
          ntp_snippets::kNotificationsFeature,
          kContentSuggestionsNotificationsFeatureVariations,
-         ntp_snippets::kNotificationsFeature.name)},
+         "ContentSuggestionsNotifications")},
     {"simplified-ntp", flag_descriptions::kSimplifiedNtpName,
      flag_descriptions::kSimplifiedNtpDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kSimplifiedNTP)},
@@ -4086,13 +4092,6 @@
      flag_descriptions::kExperimentalProductivityFeaturesDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kExperimentalProductivityFeatures)},
 
-#if defined(OS_CHROMEOS)
-    {"enable-overview-swipe-to-close",
-     flag_descriptions::kEnableOverviewSwipeToCloseName,
-     flag_descriptions::kEnableOverviewSwipeToCloseDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kOverviewSwipeToClose)},
-#endif  // OS_CHROMEOS
-
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
     {"ntp-backgrounds", flag_descriptions::kNtpBackgroundsName,
      flag_descriptions::kNtpBackgroundsDescription, kOsDesktop,
diff --git a/chrome/browser/android/vr/autocomplete_controller.cc b/chrome/browser/android/vr/autocomplete_controller.cc
index 469f4d3..1b1ac73 100644
--- a/chrome/browser/android/vr/autocomplete_controller.cc
+++ b/chrome/browser/android/vr/autocomplete_controller.cc
@@ -74,8 +74,7 @@
 void AutocompleteController::OnResultChanged(bool default_match_changed) {
   auto suggestions = std::make_unique<OmniboxSuggestions>();
   for (const auto& match : autocomplete_controller_->result()) {
-    const gfx::VectorIcon* icon = &AutocompleteMatch::TypeToVectorIcon(
-        match.type, false, match.document_type);
+    const gfx::VectorIcon* icon = &match.GetVectorIcon(false);
     suggestions->suggestions.emplace_back(OmniboxSuggestion(
         match.contents, match.description, match.contents_class,
         match.description_class, icon, match.destination_url,
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index e5f9091..290b051 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -496,8 +496,9 @@
 
   std::unique_ptr<DomainReliabilityMonitor> CreateMonitor(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
-      override {
+      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+      const domain_reliability::DomainReliabilityContext::UploadAllowedCallback&
+          upload_allowed_callback) override {
     NOTREACHED();
     return std::unique_ptr<DomainReliabilityMonitor>();
   }
@@ -545,6 +546,21 @@
   base::Callback<bool(const GURL&)> last_filter_;
 };
 
+class DomainReliablityKeyedServiceWrapper : public KeyedService {
+ public:
+  explicit DomainReliablityKeyedServiceWrapper(
+      std::unique_ptr<DomainReliabilityService> service)
+      : service_(std::move(service)) {}
+  ~DomainReliablityKeyedServiceWrapper() override = default;
+
+  DomainReliabilityService* service() { return service_.get(); }
+
+ private:
+  std::unique_ptr<DomainReliabilityService> service_;
+
+  DISALLOW_COPY_AND_ASSIGN(DomainReliablityKeyedServiceWrapper);
+};
+
 struct TestingDomainReliabilityServiceFactoryUserData
     : public base::SupportsUserData::Data {
   TestingDomainReliabilityServiceFactoryUserData(
@@ -578,7 +594,8 @@
   EXPECT_FALSE(data->attached);
 
   data->attached = true;
-  return base::WrapUnique(data->service);
+  return std::make_unique<DomainReliablityKeyedServiceWrapper>(
+      base::WrapUnique(data->service));
 }
 
 std::unique_ptr<KeyedService> BuildProtocolHandlerRegistry(
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 82544f4..e4c89b9 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -17,10 +17,10 @@
 #include "base/i18n/base_i18n_switches.h"
 #include "base/i18n/character_encoding.h"
 #include "base/json/json_reader.h"
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -594,11 +594,6 @@
 
 namespace {
 
-// Cached version of the locale so we can return the locale on the I/O
-// thread.
-base::LazyInstance<std::string>::DestructorAtExit
-    g_io_thread_application_locale = LAZY_INSTANCE_INITIALIZER;
-
 const storage::QuotaSettings* g_default_quota_settings;
 
 #if BUILDFLAG(ENABLE_PLUGINS)
@@ -658,6 +653,13 @@
   APP_LOADED_IN_TAB_SOURCE_MAX
 };
 
+// Cached version of the locale so we can return the locale on the I/O
+// thread.
+std::string& GetIOThreadApplicationLocale() {
+  static base::NoDestructor<std::string> s;
+  return *s;
+}
+
 // Returns a copy of the given url with its host set to given host and path set
 // to given path. Other parts of the url will be the same.
 GURL ReplaceURLHostAndPath(const GURL& url,
@@ -827,7 +829,7 @@
 
 void SetApplicationLocaleOnIOThread(const std::string& locale) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  g_io_thread_application_locale.Get() = locale;
+  GetIOThreadApplicationLocale() = locale;
 }
 
 // An implementation of the SSLCertReporter interface used by
@@ -1091,7 +1093,7 @@
   // before any threads are created or registered. When there are no threads,
   // we can just set the string without worrying about threadsafety.
   if (!BrowserThread::IsThreadInitialized(BrowserThread::IO)) {
-    g_io_thread_application_locale.Get() = locale;
+    GetIOThreadApplicationLocale() = locale;
     return;
   }
 
@@ -2200,7 +2202,7 @@
 
 std::string ChromeContentBrowserClient::GetApplicationLocale() {
   if (BrowserThread::CurrentlyOn(BrowserThread::IO))
-    return g_io_thread_application_locale.Get();
+    return GetIOThreadApplicationLocale();
   return g_browser_process->GetApplicationLocale();
 }
 
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 396de6d5..15b16c6 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h"
 
-#include <memory>
+#include <set>
 #include <sstream>
 #include <utility>
 
@@ -19,6 +19,7 @@
 #include "base/metrics/statistics_recorder.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -979,6 +980,76 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// AutotestPrivateSetAssistantEnabled
+///////////////////////////////////////////////////////////////////////////////
+
+AutotestPrivateSetAssistantEnabledFunction::
+    AutotestPrivateSetAssistantEnabledFunction() {
+  auto* connection = content::ServiceManagerConnection::GetForProcess();
+  assistant_state_.Init(connection->GetConnector());
+  assistant_state_.AddObserver(this);
+}
+
+AutotestPrivateSetAssistantEnabledFunction::
+    ~AutotestPrivateSetAssistantEnabledFunction() {
+  assistant_state_.RemoveObserver(this);
+}
+
+ExtensionFunction::ResponseAction
+AutotestPrivateSetAssistantEnabledFunction::Run() {
+  DVLOG(1) << "AutotestPrivateSetAssistantEnabledFunction";
+
+  std::unique_ptr<api::autotest_private::SetAssistantEnabled::Params> params(
+      api::autotest_private::SetAssistantEnabled::Params::Create(*args_));
+  EXTENSION_FUNCTION_VALIDATE(params);
+
+  Profile* profile = Profile::FromBrowserContext(browser_context());
+  if (arc::IsAssistantAllowedForProfile(profile) !=
+      ash::mojom::AssistantAllowedState::ALLOWED) {
+    return RespondNow(Error("Assistant is not available for the current user"));
+  }
+
+  profile->GetPrefs()->SetBoolean(arc::prefs::kVoiceInteractionEnabled,
+                                  params->enabled);
+  // |NOT_READY| means service not running
+  // |STOPPED| means service running but UI not shown
+  auto new_state = params->enabled
+                       ? ash::mojom::VoiceInteractionState::STOPPED
+                       : ash::mojom::VoiceInteractionState::NOT_READY;
+
+  if (assistant_state_.voice_interaction_state() == new_state)
+    return RespondNow(NoArguments());
+
+  // Assistant service has not responded yet, set up a delayed timer to wait for
+  // it and holder a reference to |this|. Also make sure we stop and respond
+  // when timeout.
+  expected_state_ = new_state;
+  timeout_timer_.Start(
+      FROM_HERE, base::TimeDelta::FromMilliseconds(params->timeout_ms),
+      base::BindOnce(&AutotestPrivateSetAssistantEnabledFunction::Timeout,
+                     this));
+  return RespondLater();
+}
+
+void AutotestPrivateSetAssistantEnabledFunction::
+    OnVoiceInteractionStatusChanged(ash::mojom::VoiceInteractionState state) {
+  DCHECK(expected_state_);
+
+  // The service could go through |NOT_READY| then to |STOPPED| during enable
+  // flow if this API is called before the initial state is reported.
+  if (expected_state_ != state)
+    return;
+
+  Respond(NoArguments());
+  expected_state_.reset();
+  timeout_timer_.AbandonAndStop();
+}
+
+void AutotestPrivateSetAssistantEnabledFunction::Timeout() {
+  Respond(Error("Assistant service timed out"));
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // AutotestPrivateAPI
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index 05a4442..e607bd93 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -5,10 +5,15 @@
 #ifndef CHROME_BROWSER_CHROMEOS_EXTENSIONS_AUTOTEST_PRIVATE_AUTOTEST_PRIVATE_API_H_
 #define CHROME_BROWSER_CHROMEOS_EXTENSIONS_AUTOTEST_PRIVATE_AUTOTEST_PRIVATE_API_H_
 
+#include <memory>
 #include <string>
+#include <vector>
 
+#include "ash/public/cpp/assistant/assistant_state_proxy.h"
+#include "ash/public/cpp/assistant/default_voice_interaction_observer.h"
 #include "ash/public/interfaces/ash_message_center_controller.mojom.h"
 #include "base/compiler_specific.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/extensions/chrome_extension_function.h"
 #include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
@@ -360,6 +365,33 @@
   chromeos::machine_learning::mojom::ModelPtr model_;
 };
 
+// Enable/disable the Google Assistant feature. This toggles the Assistant user
+// pref which will indirectly bring up or shut down the Assistant service.
+class AutotestPrivateSetAssistantEnabledFunction
+    : public UIThreadExtensionFunction,
+      public ash::DefaultVoiceInteractionObserver {
+ public:
+  AutotestPrivateSetAssistantEnabledFunction();
+  DECLARE_EXTENSION_FUNCTION("autotestPrivate.setAssistantEnabled",
+                             AUTOTESTPRIVATE_SETASSISTANTENABLED)
+
+ private:
+  ~AutotestPrivateSetAssistantEnabledFunction() override;
+  ResponseAction Run() override;
+
+  // ash::DefaultVoiceInteractionObserver overrides:
+  void OnVoiceInteractionStatusChanged(
+      ash::mojom::VoiceInteractionState state) override;
+
+  // Called when the Assistant service does not respond in a timely fashion. We
+  // will respond with an error.
+  void Timeout();
+
+  ash::AssistantStateProxy assistant_state_;
+  base::Optional<ash::mojom::VoiceInteractionState> expected_state_;
+  base::OneShotTimer timeout_timer_;
+};
+
 // The profile-keyed service that manages the autotestPrivate extension API.
 class AutotestPrivateAPI : public BrowserContextKeyedAPI {
  public:
diff --git a/chrome/browser/domain_reliability/service_factory.cc b/chrome/browser/domain_reliability/service_factory.cc
index ba9957c..d6d7c4c 100644
--- a/chrome/browser/domain_reliability/service_factory.cc
+++ b/chrome/browser/domain_reliability/service_factory.cc
@@ -11,6 +11,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "components/domain_reliability/service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/browser_context.h"
 
 #if defined(OS_CHROMEOS)
@@ -31,15 +32,32 @@
 // Identifies Chrome as the source of Domain Reliability uploads it sends.
 const char kUploadReporterString[] = "chrome";
 
+class KeyedServiceWrapper : public KeyedService {
+ public:
+  explicit KeyedServiceWrapper(DomainReliabilityService* service)
+      : service_(service) {}
+  ~KeyedServiceWrapper() override = default;
+
+  DomainReliabilityService* service() { return service_; }
+
+ private:
+  DomainReliabilityService* service_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeyedServiceWrapper);
+};
+
 }  // namespace
 
 // static
 DomainReliabilityService*
 DomainReliabilityServiceFactory::GetForBrowserContext(
     content::BrowserContext* context) {
-  return static_cast<DomainReliabilityService*>(
+  auto* wrapper = static_cast<KeyedServiceWrapper*>(
       GetInstance()->GetServiceForBrowserContext(context,
                                                  /* create = */ true));
+  if (!wrapper)
+    return nullptr;
+  return wrapper->service();
 }
 
 // static
@@ -60,7 +78,8 @@
   if (!ShouldCreateService())
     return NULL;
 
-  return DomainReliabilityService::Create(kUploadReporterString, context);
+  return new KeyedServiceWrapper(
+      DomainReliabilityService::Create(kUploadReporterString));
 }
 
 bool DomainReliabilityServiceFactory::ShouldCreateService() const {
diff --git a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
index 4e9bf45..5e55faf2 100644
--- a/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc
@@ -67,6 +67,7 @@
   std::string InitClientId() override { return client_id_; }
   std::string InitEnrollmentToken() override { return std::string(); }
   std::string InitDMToken() override { return std::string(); }
+  bool InitEnrollmentErrorOption() override { return true; }
   void SaveDMToken(const std::string& token) override {}
 
  private:
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
index b9f0f646..8cc89e22 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
@@ -8,7 +8,7 @@
 
 #include <memory>
 
-#include "base/lazy_instance.h"
+#include "base/no_destructor.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/api/web_navigation/web_navigation_api_constants.h"
 #include "chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h"
@@ -39,10 +39,13 @@
 
 namespace {
 
-typedef std::map<content::WebContents*, WebNavigationTabObserver*>
-    TabObserverMap;
-static base::LazyInstance<TabObserverMap>::DestructorAtExit g_tab_observer =
-    LAZY_INSTANCE_INITIALIZER;
+using TabObserverMap =
+    std::map<content::WebContents*, WebNavigationTabObserver*>;
+
+TabObserverMap& GetTabObserverMap() {
+  static base::NoDestructor<TabObserverMap> s;
+  return *s;
+}
 
 }  // namespace
 
@@ -121,7 +124,6 @@
     const content::NotificationSource& source,
     const content::NotificationDetails& details) {
   switch (type) {
-
     case chrome::NOTIFICATION_TAB_ADDED:
       TabAdded(content::Details<content::WebContents>(details).ptr());
       break;
@@ -212,7 +214,7 @@
 WebNavigationTabObserver::WebNavigationTabObserver(
     content::WebContents* web_contents)
     : WebContentsObserver(web_contents) {
-  g_tab_observer.Get().insert(TabObserverMap::value_type(web_contents, this));
+  GetTabObserverMap().insert(TabObserverMap::value_type(web_contents, this));
   navigation_state_.FrameHostCreated(web_contents->GetMainFrame());
 }
 
@@ -221,8 +223,8 @@
 // static
 WebNavigationTabObserver* WebNavigationTabObserver::Get(
     content::WebContents* web_contents) {
-  auto i = g_tab_observer.Get().find(web_contents);
-  return i == g_tab_observer.Get().end() ? NULL : i->second;
+  auto i = GetTabObserverMap().find(web_contents);
+  return i == GetTabObserverMap().end() ? NULL : i->second;
 }
 
 void WebNavigationTabObserver::RenderFrameDeleted(
@@ -400,7 +402,7 @@
 }
 
 void WebNavigationTabObserver::WebContentsDestroyed() {
-  g_tab_observer.Get().erase(web_contents());
+  GetTabObserverMap().erase(web_contents());
   registrar_.RemoveAll();
 }
 
diff --git a/chrome/browser/extensions/install_signer.cc b/chrome/browser/extensions/install_signer.cc
index 93fe6da..d0aa12d 100644
--- a/chrome/browser/extensions/install_signer.cc
+++ b/chrome/browser/extensions/install_signer.cc
@@ -279,7 +279,7 @@
 
 namespace {
 
-static int g_request_count = 0;
+int g_request_count = 0;
 
 base::LazyInstance<base::TimeTicks>::DestructorAtExit g_last_request_time =
     LAZY_INSTANCE_INITIALIZER;
diff --git a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
index 568a461b..55f89e4 100644
--- a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
+++ b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
 #include "chrome/browser/extensions/browsertest_util.h"
+#include "components/update_client/protocol_handler.h"
 #include "components/update_client/url_loader_post_interceptor.h"
 #include "content/public/browser/browser_context.h"
 #include "extensions/browser/updater/update_service.h"
@@ -30,22 +31,33 @@
     : public extensions::ChromeUpdateClientConfig {
  public:
   TestChromeUpdateClientConfig(content::BrowserContext* context,
+                               bool use_JSON,
                                const std::vector<GURL>& update_url,
                                const std::vector<GURL>& ping_url)
       : extensions::ChromeUpdateClientConfig(context),
+        use_JSON_(use_JSON),
         update_url_(update_url),
         ping_url_(ping_url) {}
 
+  // Overrides for update_client::Configurator.
   std::vector<GURL> UpdateUrl() const final { return update_url_; }
 
   std::vector<GURL> PingUrl() const final { return ping_url_; }
 
   bool EnabledCupSigning() const final { return false; }
 
+  std::unique_ptr<update_client::ProtocolHandlerFactory>
+  GetProtocolHandlerFactory() const final {
+    if (use_JSON_)
+      return std::make_unique<update_client::ProtocolHandlerFactoryJSON>();
+    return std::make_unique<update_client::ProtocolHandlerFactoryXml>();
+  }
+
  protected:
   ~TestChromeUpdateClientConfig() override = default;
 
  private:
+  bool use_JSON_ = false;
   std::vector<GURL> update_url_;
   std::vector<GURL> ping_url_;
 
@@ -105,9 +117,10 @@
 
 }  // namespace
 
-ExtensionUpdateClientBaseTest::ExtensionUpdateClientBaseTest()
+ExtensionUpdateClientBaseTest::ExtensionUpdateClientBaseTest(bool use_JSON)
     : https_server_for_update_(net::EmbeddedTestServer::TYPE_HTTPS),
-      https_server_for_ping_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+      https_server_for_ping_(net::EmbeddedTestServer::TYPE_HTTPS),
+      use_JSON_(use_JSON) {}
 
 ExtensionUpdateClientBaseTest::~ExtensionUpdateClientBaseTest() {}
 
@@ -123,12 +136,12 @@
 ExtensionUpdateClientBaseTest::ChromeUpdateClientConfigFactory() const {
   return base::BindRepeating(
       [](const std::vector<GURL>& update_url, const std::vector<GURL>& ping_url,
-         content::BrowserContext* context)
+         bool use_JSON, content::BrowserContext* context)
           -> scoped_refptr<ChromeUpdateClientConfig> {
         return base::MakeRefCounted<TestChromeUpdateClientConfig>(
-            context, update_url, ping_url);
+            context, use_JSON, update_url, ping_url);
       },
-      GetUpdateUrls(), GetPingUrls());
+      GetUpdateUrls(), GetPingUrls(), use_JSON_);
 }
 
 void ExtensionUpdateClientBaseTest::SetUp() {
diff --git a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
index 025e784..dd17e73 100644
--- a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
+++ b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.h
@@ -5,6 +5,11 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_UPDATER_EXTENSION_UPDATE_CLIENT_BASE_BROWSERTEST_H_
 #define CHROME_BROWSER_EXTENSIONS_UPDATER_EXTENSION_UPDATE_CLIENT_BASE_BROWSERTEST_H_
 
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/updater/chrome_update_client_config.h"
@@ -35,7 +40,7 @@
  public:
   using ConfigFactoryCallback = ChromeUpdateClientConfig::FactoryCallback;
 
-  ExtensionUpdateClientBaseTest();
+  explicit ExtensionUpdateClientBaseTest(bool use_JSON);
   ~ExtensionUpdateClientBaseTest() override;
 
   // ExtensionBrowserTest:
@@ -82,6 +87,8 @@
   net::EmbeddedTestServer https_server_for_update_;
   net::EmbeddedTestServer https_server_for_ping_;
 
+  bool use_JSON_ = false;
+
  private:
   bool OnRequest(content::URLLoaderInterceptor::RequestParams* params);
 
diff --git a/chrome/browser/extensions/updater/update_service_browsertest.cc b/chrome/browser/extensions/updater/update_service_browsertest.cc
index 5141dae..7e2c0b8f 100644
--- a/chrome/browser/extensions/updater/update_service_browsertest.cc
+++ b/chrome/browser/extensions/updater/update_service_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/command_line.h"
+#include "base/json/json_reader.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -38,9 +39,10 @@
 
 }  // namespace
 
-class UpdateServiceTest : public ExtensionUpdateClientBaseTest {
+class UpdateServiceTest : public ExtensionUpdateClientBaseTest,
+                          public testing::WithParamInterface<bool> {
  public:
-  UpdateServiceTest() {}
+  UpdateServiceTest() : ExtensionUpdateClientBaseTest(GetParam()) {}
   ~UpdateServiceTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -53,17 +55,27 @@
   bool ShouldEnableContentVerification() override { return true; }
 };
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, NoUpdate) {
+// This test is parameterized for using JSON or XML serialization. |true| means
+// JSON serialization is used.
+INSTANTIATE_TEST_CASE_P(Parameterized, UpdateServiceTest, testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, NoUpdate) {
   // Verify that UpdateService runs correctly when there's no update.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock a no-update response.
-  const base::FilePath update_response =
-      test_data_dir_.AppendASCII("updater/updatecheck_reply_noupdate_1.xml");
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      update_response));
+  if (use_JSON_) {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        test_data_dir_.AppendASCII(
+            "updater/updatecheck_reply_noupdate_1.json")));
+  } else {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        test_data_dir_.AppendASCII(
+            "updater/updatecheck_reply_noupdate_1.xml")));
+  }
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   const Extension* extension =
@@ -104,22 +116,36 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.10")", kExtensionId)));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.10", app.FindKey("version")->GetString());
+    EXPECT_TRUE(app.FindKey("enabled")->GetBool());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.10")", kExtensionId)));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UpdateCheckError) {
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, UpdateCheckError) {
   // Verify that UpdateService works correctly when there's an error in the
   // update check phase.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock an update check error.
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      net::HTTP_FORBIDDEN));
+  if (use_JSON_) {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        net::HTTP_FORBIDDEN));
+  } else {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        net::HTTP_FORBIDDEN));
+  }
 
   const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   const Extension* extension =
@@ -162,25 +188,42 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.10")", kExtensionId)));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.10", app.FindKey("version")->GetString());
+    EXPECT_TRUE(app.FindKey("enabled")->GetBool());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.10")", kExtensionId)));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, TwoUpdateCheckErrors) {
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, TwoUpdateCheckErrors) {
   // Verify that the UMA counters are emitted properly when there are 2 update
   // checks with different number of extensions, both of which result in errors.
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock update check errors.
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      net::HTTP_NOT_MODIFIED));
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      net::HTTP_USE_PROXY));
+  if (use_JSON_) {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        net::HTTP_NOT_MODIFIED));
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        net::HTTP_USE_PROXY));
+  } else {
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        net::HTTP_NOT_MODIFIED));
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        net::HTTP_USE_PROXY));
+  }
 
   const base::FilePath crx_path1 = test_data_dir_.AppendASCII("updater/v1.crx");
   const base::FilePath crx_path2 = test_data_dir_.AppendASCII("updater/v2.crx");
@@ -230,23 +273,36 @@
       << ping_interceptor_->GetRequestsAsString();
 }
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, SuccessfulUpdate) {
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, SuccessfulUpdate) {
   base::ScopedAllowBlockingForTesting allow_io;
   base::HistogramTester histogram_tester;
 
   // Mock an update response.
-  const base::FilePath update_response =
-      test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-  const base::FilePath ping_response =
-      test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
+  if (use_JSON_) {
+    const base::FilePath update_response =
+        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
+    const base::FilePath ping_response =
+        test_data_dir_.AppendASCII("updater/ping_reply_1.json");
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        update_response));
+    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+        ping_response));
+  } else {
+    const base::FilePath update_response =
+        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
+    const base::FilePath ping_response =
+        test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        update_response));
+    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("eventtype"),
+        ping_response));
+  }
 
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      update_response));
-  ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("eventtype"),
-      ping_response));
+  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   set_interceptor_hook(base::BindLambdaForTesting(
       [&](content::URLLoaderInterceptor::RequestParams* params) {
         if (params->url_request.url.path() != "/download/v1.crx")
@@ -295,30 +351,51 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.10")", kExtensionId)));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.10", app.FindKey("version")->GetString());
+    EXPECT_TRUE(app.FindKey("enabled")->GetBool());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.10")", kExtensionId)));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(R"(enabled="1")"));
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, PolicyCorrupted) {
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, PolicyCorrupted) {
   base::ScopedAllowBlockingForTesting allow_io;
 
   ExtensionSystem* system = ExtensionSystem::Get(profile());
   ExtensionService* service = extension_service();
 
-  const base::FilePath update_response =
-      test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-  const base::FilePath ping_response =
-      test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
+  if (use_JSON_) {
+    const base::FilePath update_response =
+        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
+    const base::FilePath ping_response =
+        test_data_dir_.AppendASCII("updater/ping_reply_1.json");
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+        update_response));
+    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+        ping_response));
+  } else {
+    const base::FilePath update_response =
+        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
+    const base::FilePath ping_response =
+        test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
+    ASSERT_TRUE(update_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+        update_response));
+    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+        std::make_unique<update_client::PartialMatch>("eventtype"),
+        ping_response));
+  }
 
-  ASSERT_TRUE(update_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-      update_response));
-  ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-      std::make_unique<update_client::PartialMatch>("eventtype"),
-      ping_response));
+  const base::FilePath crx_path = test_data_dir_.AppendASCII("updater/v1.crx");
   set_interceptor_hook(base::BindLambdaForTesting(
       [&](content::URLLoaderInterceptor::RequestParams* params) {
         if (params->url_request.url.path() != "/download/v1.crx")
@@ -379,19 +456,32 @@
   // - <disabled reason="1024"/>
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.0.0.0")", kExtensionId)));
-  EXPECT_THAT(
-      update_request,
-      ::testing::HasSubstr(
-          R"(installsource="reinstall" installedby="policy" enabled="0")"));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                  R"(<disabled reason="%d"/>)",
-                                  disable_reason::DISABLE_CORRUPTED)));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kExtensionId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
+    EXPECT_EQ("reinstall", app.FindKey("installsource")->GetString());
+    EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+    EXPECT_FALSE(app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList()[0];
+    EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
+              disabled.FindKey("reason")->GetInt());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.0.0.0")", kExtensionId)));
+    EXPECT_THAT(
+        update_request,
+        ::testing::HasSubstr(
+            R"(installsource="reinstall" installedby="policy" enabled="0")"));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
+                                    R"(<disabled reason="%d"/>)",
+                                    disable_reason::DISABLE_CORRUPTED)));
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(UpdateServiceTest, UninstallExtensionWhileUpdating) {
+IN_PROC_BROWSER_TEST_P(UpdateServiceTest, UninstallExtensionWhileUpdating) {
   // This test is to verify that the extension updater engine (update client)
   // works correctly when an extension is uninstalled when the extension updater
   // is in progress.
@@ -427,9 +517,10 @@
   EXPECT_EQ(0, get_interceptor_count());
 }
 
-class PolicyUpdateServiceTest : public ExtensionUpdateClientBaseTest {
+class PolicyUpdateServiceTest : public ExtensionUpdateClientBaseTest,
+                                public testing::WithParamInterface<bool> {
  public:
-  PolicyUpdateServiceTest() {}
+  PolicyUpdateServiceTest() : ExtensionUpdateClientBaseTest(GetParam()) {}
   ~PolicyUpdateServiceTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -465,11 +556,6 @@
 
     const base::FilePath crx_path =
         test_data_dir_.AppendASCII("updater/v1.crx");
-    const base::FilePath update_response =
-        test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
-    const base::FilePath ping_response =
-        test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
-
     set_interceptor_hook(base::BindLambdaForTesting(
         [=](content::URLLoaderInterceptor::RequestParams* params) {
           if (params->url_request.url.path() != "/download/v1.crx")
@@ -479,30 +565,67 @@
                                                        params->client.get());
           return true;
         }));
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(update_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
-        update_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
-    ASSERT_TRUE(ping_interceptor_->ExpectRequest(
-        std::make_unique<update_client::PartialMatch>("eventtype"),
-        ping_response));
+    if (use_JSON_) {
+      const base::FilePath update_response =
+          test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.json");
+      const base::FilePath ping_response =
+          test_data_dir_.AppendASCII("updater/ping_reply_1.json");
+
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("updatecheck":{)"),
+          update_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>(R"("eventtype":)"),
+          ping_response));
+    } else {
+      const base::FilePath update_response =
+          test_data_dir_.AppendASCII("updater/updatecheck_reply_update_1.xml");
+      const base::FilePath ping_response =
+          test_data_dir_.AppendASCII("updater/ping_reply_1.xml");
+
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+          update_response));
+      ASSERT_TRUE(update_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("<updatecheck/>"),
+          update_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("eventtype"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("eventtype"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("eventtype"),
+          ping_response));
+      ASSERT_TRUE(ping_interceptor_->ExpectRequest(
+          std::make_unique<update_client::PartialMatch>("eventtype"),
+          ping_response));
+    }
   }
 
   std::vector<GURL> GetUpdateUrls() const override {
@@ -523,10 +646,16 @@
   content_verifier_test::DownloaderTestDelegate downloader_;
 };
 
+// This test is parameterized for using JSON or XML serialization. |true| means
+// JSON serialization is used.
+INSTANTIATE_TEST_CASE_P(Parameterized,
+                        PolicyUpdateServiceTest,
+                        testing::Bool());
+
 // Tests that if CheckForExternalUpdates() fails, then we retry reinstalling
 // corrupted policy extensions. For example: if network is unavailable,
 // CheckForExternalUpdates() will fail.
-IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, FailedUpdateRetries) {
+IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, FailedUpdateRetries) {
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
   ExtensionService* service = extension_service();
   ContentVerifier* verifier =
@@ -573,19 +702,32 @@
   // - <disabled reason="1024"/>
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
-  EXPECT_THAT(
-      update_request,
-      ::testing::HasSubstr(
-          R"(installsource="reinstall" installedby="policy" enabled="0")"));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                  R"(<disabled reason="%d"/>)",
-                                  disable_reason::DISABLE_CORRUPTED)));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(id_, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
+    EXPECT_EQ("reinstall", app.FindKey("installsource")->GetString());
+    EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+    EXPECT_FALSE(app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList()[0];
+    EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
+              disabled.FindKey("reason")->GetInt());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
+    EXPECT_THAT(
+        update_request,
+        ::testing::HasSubstr(
+            R"(installsource="reinstall" installedby="policy" enabled="0")"));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
+                                    R"(<disabled reason="%d"/>)",
+                                    disable_reason::DISABLE_CORRUPTED)));
+  }
 }
 
-IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, Backoff) {
+IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, Backoff) {
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
   ContentVerifier* verifier =
       ExtensionSystem::Get(profile())->content_verifier();
@@ -637,7 +779,7 @@
 
 // We want to test what happens at startup with a corroption-disabled policy
 // force installed extension. So we set that up in the PRE test here.
-IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, PRE_PolicyCorruptedOnStartup) {
+IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, PRE_PolicyCorruptedOnStartup) {
   // This is to not allow any corrupted resintall to proceed.
   content_verifier_test::DelayTracker delay_tracker;
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
@@ -666,7 +808,7 @@
 }
 
 // Now actually test what happens on the next startup after the PRE test above.
-IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, PolicyCorruptedOnStartup) {
+IN_PROC_BROWSER_TEST_P(PolicyUpdateServiceTest, PolicyCorruptedOnStartup) {
   // Depdending on timing, the extension may have already been reinstalled
   // between SetUpInProcessBrowserTestFixture and now (usually not during local
   // testing on a developer machine, but sometimes on a heavily loaded system
@@ -691,16 +833,29 @@
 
   const std::string update_request =
       std::get<0>(update_interceptor_->GetRequests()[0]);
-  EXPECT_THAT(update_request,
-              ::testing::HasSubstr(base::StringPrintf(
-                  R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
-  EXPECT_THAT(
-      update_request,
-      ::testing::HasSubstr(
-          R"(installsource="reinstall" installedby="policy" enabled="0")"));
-  EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
-                                  R"(<disabled reason="%d"/>)",
-                                  disable_reason::DISABLE_CORRUPTED)));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(update_request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(id_, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.0.0.0", app.FindKey("version")->GetString());
+    EXPECT_EQ("reinstall", app.FindKey("installsource")->GetString());
+    EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+    EXPECT_FALSE(app.FindKey("enabled")->GetBool());
+    const auto& disabled = app.FindKey("disabled")->GetList()[0];
+    EXPECT_EQ(disable_reason::DISABLE_CORRUPTED,
+              disabled.FindKey("reason")->GetInt());
+  } else {
+    EXPECT_THAT(update_request,
+                ::testing::HasSubstr(base::StringPrintf(
+                    R"(<app appid="%s" version="0.0.0.0")", id_.c_str())));
+    EXPECT_THAT(
+        update_request,
+        ::testing::HasSubstr(
+            R"(installsource="reinstall" installedby="policy" enabled="0")"));
+    EXPECT_THAT(update_request, ::testing::HasSubstr(base::StringPrintf(
+                                    R"(<disabled reason="%d"/>)",
+                                    disable_reason::DISABLE_CORRUPTED)));
+  }
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/first_run/first_run.cc b/chrome/browser/first_run/first_run.cc
index c0a4d14..47c026f 100644
--- a/chrome/browser/first_run/first_run.cc
+++ b/chrome/browser/first_run/first_run.cc
@@ -11,11 +11,11 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
+#include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
@@ -265,28 +265,27 @@
   std::transform(src.begin(), src.end(), ret->begin(), &UrlFromString);
 }
 
-static base::LazyInstance<base::FilePath>::DestructorAtExit
-    master_prefs_path_for_testing = LAZY_INSTANCE_INITIALIZER;
+base::FilePath& GetMasterPrefsPathForTesting() {
+  static base::NoDestructor<base::FilePath> s;
+  return *s;
+}
 
 // Loads master preferences from the master preference file into the installer
 // master preferences. Returns the pointer to installer::MasterPreferences
-// object if successful; otherwise, returns NULL.
-installer::MasterPreferences* LoadMasterPrefs() {
+// object if successful; otherwise, returns nullptr.
+std::unique_ptr<installer::MasterPreferences> LoadMasterPrefs() {
   base::FilePath master_prefs_path;
-  if (!master_prefs_path_for_testing.Get().empty()) {
-    master_prefs_path = master_prefs_path_for_testing.Get();
-  } else {
+  if (!GetMasterPrefsPathForTesting().empty())
+    master_prefs_path = GetMasterPrefsPathForTesting();
+  else
     master_prefs_path = base::FilePath(first_run::internal::MasterPrefsPath());
-  }
-  if (master_prefs_path.empty())
-    return NULL;
-  installer::MasterPreferences* install_prefs =
-      new installer::MasterPreferences(master_prefs_path);
-  if (!install_prefs->read_from_file()) {
-    delete install_prefs;
-    return NULL;
-  }
 
+  if (master_prefs_path.empty())
+    return nullptr;
+  auto install_prefs =
+      std::make_unique<installer::MasterPreferences>(master_prefs_path);
+  if (!install_prefs->read_from_file())
+    return nullptr;
   return install_prefs;
 }
 
@@ -489,7 +488,7 @@
 }
 
 void SetMasterPrefsPathForTesting(const base::FilePath& master_prefs) {
-  master_prefs_path_for_testing.Get() = master_prefs;
+  GetMasterPrefsPathForTesting() = master_prefs;
 }
 
 ProcessMasterPreferencesResult ProcessMasterPreferences(
@@ -497,8 +496,8 @@
     MasterPrefs* out_prefs) {
   DCHECK(!user_data_dir.empty());
 
-  std::unique_ptr<installer::MasterPreferences> install_prefs(
-      LoadMasterPrefs());
+  std::unique_ptr<installer::MasterPreferences> install_prefs =
+      LoadMasterPrefs();
 
   if (install_prefs.get()) {
     if (!internal::ShowPostInstallEULAIfNeeded(install_prefs.get()))
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index e1cc3a3..b70c920 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -700,12 +700,6 @@
 const char kEnableOutOfBlinkCORSDescription[] =
     "CORS handling logic is moved out of blink.";
 
-const char kEnableOverviewSwipeToCloseName[] =
-    "Enable overview swipe to close.";
-const char kEnableOverviewSwipeToCloseDescription[] =
-    "Enables closing of items in overview mode by dragging or flinging "
-    "vertically.";
-
 const char kExperimentalAccessibilityFeaturesName[] =
     "Experimental accessibility features";
 const char kExperimentalAccessibilityFeaturesDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index b6b2b6a6..ab10885 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -448,9 +448,6 @@
 extern const char kEnableOutOfBlinkCORSName[];
 extern const char kEnableOutOfBlinkCORSDescription[];
 
-extern const char kEnableOverviewSwipeToCloseName[];
-extern const char kEnableOverviewSwipeToCloseDescription[];
-
 extern const char kVizDisplayCompositorName[];
 extern const char kVizDisplayCompositorDescription[];
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
index f4f41ba..031a907a 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
@@ -464,27 +464,3 @@
         "AnchorElementMetrics.Clicked.OnNonDSE.SameHost", 0, 1);
   }
 }
-
-// Tests that the browser only receives anchor elements that are in the
-// viewport, and from anchor elements whose target differ from document URL
-// by one digit.
-IN_PROC_BROWSER_TEST_P(NavigationPredictorBrowserTest,
-                       ViewportOnlyAndUrlIncrementByOne) {
-  base::HistogramTester histogram_tester;
-
-  const GURL& url = GetTestURL("/long_page_with_anchors-1.html");
-  ui_test_utils::NavigateToURL(browser(), url);
-  base::RunLoop().RunUntilIdle();
-
-  if (base::FeatureList::IsEnabled(
-          blink::features::kRecordAnchorMetricsVisible)) {
-    histogram_tester.ExpectUniqueSample(
-        "AnchorElementMetrics.Visible.NumberOfAnchorElements", 2, 1);
-    // Same document anchor element should be removed after merge.
-    histogram_tester.ExpectUniqueSample(
-        "AnchorElementMetrics.Visible.NumberOfAnchorElementsAfterMerge", 2, 1);
-  } else {
-    histogram_tester.ExpectTotalCount(
-        "AnchorElementMetrics.Visible.NumberOfAnchorElements", 0);
-  }
-}
diff --git a/chrome/browser/offline_pages/auto_fetch_page_load_watcher.cc b/chrome/browser/offline_pages/auto_fetch_page_load_watcher.cc
new file mode 100644
index 0000000..aff91a4a
--- /dev/null
+++ b/chrome/browser/offline_pages/auto_fetch_page_load_watcher.cc
@@ -0,0 +1,152 @@
+// 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/offline_pages/auto_fetch_page_load_watcher.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/offline_pages/offline_page_auto_fetcher.h"
+#include "chrome/browser/offline_pages/offline_page_auto_fetcher_service.h"
+#include "chrome/browser/offline_pages/offline_page_auto_fetcher_service_factory.h"
+#include "chrome/browser/offline_pages/request_coordinator_factory.h"
+#include "components/offline_pages/core/background/request_coordinator.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace offline_pages {
+
+// Observes a WebContents to relay navigation events to
+// AutoFetchPageLoadWatcher.
+class AutoFetchPageLoadWatcher::NavigationObserver
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<
+          AutoFetchPageLoadWatcher::NavigationObserver> {
+ public:
+  explicit NavigationObserver(content::WebContents* web_contents)
+      : content::WebContentsObserver(web_contents) {
+    helper_ = OfflinePageAutoFetcherServiceFactory::GetForBrowserContext(
+                  web_contents->GetBrowserContext())
+                  ->page_load_watcher();
+    DCHECK(helper_);
+  }
+
+  // content::WebContentsObserver implementation.
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override {
+    if (!navigation_handle->IsInMainFrame() ||
+        !navigation_handle->HasCommitted() || navigation_handle->IsErrorPage())
+      return;
+
+    // Note: The redirect chain includes the final URL. We consider all URLs
+    // along the redirect chain as successful.
+    for (const auto& u : navigation_handle->GetRedirectChain()) {
+      helper_->HandlePageNavigation(u);
+    }
+  }
+
+ private:
+  AutoFetchPageLoadWatcher* helper_;
+
+  DISALLOW_COPY_AND_ASSIGN(NavigationObserver);
+};
+
+// static
+void AutoFetchPageLoadWatcher::CreateForWebContents(
+    content::WebContents* web_contents) {
+  OfflinePageAutoFetcherService* service =
+      OfflinePageAutoFetcherServiceFactory::GetForBrowserContext(
+          web_contents->GetBrowserContext());
+  // Don't try to create if the service isn't available (happens in incognito
+  // mode).
+  if (service) {
+    NavigationObserver::CreateForWebContents(web_contents);
+  }
+}
+
+AutoFetchPageLoadWatcher::AutoFetchPageLoadWatcher(
+    RequestCoordinator* request_coordinator)
+    : request_coordinator_(request_coordinator) {
+  request_coordinator_->AddObserver(this);
+  request_coordinator_->GetAllRequests(
+      base::BindOnce(&AutoFetchPageLoadWatcher::ObserverInitialize,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+AutoFetchPageLoadWatcher::~AutoFetchPageLoadWatcher() {
+  request_coordinator_->RemoveObserver(this);
+}
+
+void AutoFetchPageLoadWatcher::OnAdded(const SavePageRequest& request) {
+  if (!observer_ready_)
+    return;
+  if (request.client_id().name_space == kAutoAsyncNamespace) {
+    live_auto_fetch_requests_[request.url()].push_back(request.request_id());
+  }
+}
+
+void AutoFetchPageLoadWatcher::OnCompleted(
+    const SavePageRequest& request,
+    RequestNotifier::BackgroundSavePageResult status) {
+  if (!observer_ready_)
+    return;
+  // A SavePageRequest is complete, remove our bookeeping of it in
+  // |live_auto_fetch_requests_|.
+  auto iter = live_auto_fetch_requests_.find(request.url());
+  if (iter != live_auto_fetch_requests_.end()) {
+    std::vector<int64_t>& id_list = iter->second;
+    auto id_iter =
+        std::find(id_list.begin(), id_list.end(), request.request_id());
+    if (id_iter != id_list.end()) {
+      id_list.erase(id_iter);
+      if (id_list.empty())
+        live_auto_fetch_requests_.erase(iter);
+    }
+  }
+}
+
+void AutoFetchPageLoadWatcher::RemoveRequests(
+    const std::vector<int64_t>& request_ids) {
+  request_coordinator_->RemoveRequests(request_ids, base::DoNothing());
+}
+
+void AutoFetchPageLoadWatcher::HandlePageNavigation(const GURL& url) {
+  // Early exit for the common-case.
+  if (observer_ready_ && live_auto_fetch_requests_.empty()) {
+    return;
+  }
+
+  // Note: It is possible that this method is called before
+  // ObserverInitialized, in which case we have to defer handling of the
+  // event. Never accumulate more than a few, so we can't have a boundless
+  // array if ObserverInitialize is never called.
+  if (!observer_ready_) {
+    if (pages_loaded_before_observer_ready_.size() < 10)
+      pages_loaded_before_observer_ready_.push_back(url);
+    return;
+  }
+
+  auto iter = live_auto_fetch_requests_.find(url);
+  if (iter == live_auto_fetch_requests_.end())
+    return;
+  RemoveRequests(iter->second);
+}
+
+void AutoFetchPageLoadWatcher::ObserverInitialize(
+    std::vector<std::unique_ptr<SavePageRequest>> all_requests) {
+  observer_ready_ = true;
+  for (const std::unique_ptr<SavePageRequest>& request : all_requests) {
+    OnAdded(*request);
+  }
+  for (const GURL& url : pages_loaded_before_observer_ready_) {
+    HandlePageNavigation(url);
+  }
+  pages_loaded_before_observer_ready_.clear();
+}
+
+}  // namespace offline_pages
diff --git a/chrome/browser/offline_pages/auto_fetch_page_load_watcher.h b/chrome/browser/offline_pages/auto_fetch_page_load_watcher.h
new file mode 100644
index 0000000..fa70625
--- /dev/null
+++ b/chrome/browser/offline_pages/auto_fetch_page_load_watcher.h
@@ -0,0 +1,76 @@
+// 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_OFFLINE_PAGES_AUTO_FETCH_PAGE_LOAD_WATCHER_H_
+#define CHROME_BROWSER_OFFLINE_PAGES_AUTO_FETCH_PAGE_LOAD_WATCHER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/offline_pages/core/background/request_coordinator.h"
+#include "components/offline_pages/core/client_id.h"
+#include "url/gurl.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace offline_pages {
+class SavePageRequest;
+class RequestCoordinator;
+
+// Cancels active auto fetch requests if a page loads.
+class AutoFetchPageLoadWatcher : public RequestCoordinator::Observer {
+ public:
+  static void CreateForWebContents(content::WebContents* web_contents);
+
+  explicit AutoFetchPageLoadWatcher(RequestCoordinator* request_coordinator);
+  ~AutoFetchPageLoadWatcher() override;
+
+  // RequestCoordinator::Observer methods. These keep
+  // |live_auto_fetch_requests_| in sync with the state of RequestCoordinator.
+  void OnAdded(const SavePageRequest& request) override;
+  void OnCompleted(const SavePageRequest& request,
+                   RequestNotifier::BackgroundSavePageResult status) override;
+  void OnChanged(const SavePageRequest& request) override {}
+  void OnNetworkProgress(const SavePageRequest& request,
+                         int64_t received_bytes) override {}
+
+ protected:
+  class NavigationObserver;
+
+  // Virtual for testing only.
+  virtual void RemoveRequests(const std::vector<int64_t>& request_ids);
+
+  // Called when navigation completes. Triggers cancellation of any
+  // in-flight auto fetch requests that match a successful navigation.
+  void HandlePageNavigation(const GURL& url);
+
+  void ObserverInitialize(
+      std::vector<std::unique_ptr<SavePageRequest>> all_requests);
+
+  offline_pages::RequestCoordinator* GetRequestCoordinator();
+
+  // Whether ObserverInitialize has been called.
+  bool observer_ready_ = false;
+
+  RequestCoordinator* request_coordinator_;
+  std::vector<GURL> pages_loaded_before_observer_ready_;
+
+  // This represents the set of auto fetch request IDs currently in the
+  // RequestCoordinator's queue, mapped by URL.
+  std::map<GURL, std::vector<int64_t>> live_auto_fetch_requests_;
+
+  base::WeakPtrFactory<AutoFetchPageLoadWatcher> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(AutoFetchPageLoadWatcher);
+};
+
+}  // namespace offline_pages
+
+#endif  // CHROME_BROWSER_OFFLINE_PAGES_AUTO_FETCH_PAGE_LOAD_WATCHER_H_
diff --git a/chrome/browser/offline_pages/auto_fetch_page_load_watcher_unittest.cc b/chrome/browser/offline_pages/auto_fetch_page_load_watcher_unittest.cc
new file mode 100644
index 0000000..685f905
--- /dev/null
+++ b/chrome/browser/offline_pages/auto_fetch_page_load_watcher_unittest.cc
@@ -0,0 +1,261 @@
+// 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/offline_pages/auto_fetch_page_load_watcher.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/test/bind_test_util.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/offline_pages/request_coordinator_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/offline_pages/core/background/request_coordinator_stub_taco.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "content/public/test/navigation_simulator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace offline_pages {
+namespace {
+using content::NavigationSimulator;
+
+GURL TestURL() {
+  return GURL("http://www.url.com");
+}
+
+SavePageRequest TestRequest(int64_t id, const GURL& url = TestURL()) {
+  return SavePageRequest(id, url,
+                         ClientId(kAutoAsyncNamespace, std::to_string(id)),
+                         base::Time::Now(), false);
+}
+
+// For access to protected methods.
+class TestAutoFetchPageLoadWatcher : public AutoFetchPageLoadWatcher {
+ public:
+  explicit TestAutoFetchPageLoadWatcher(RequestCoordinator* request_coordinator)
+      : AutoFetchPageLoadWatcher(request_coordinator) {}
+  void RemoveRequests(const std::vector<int64_t>& request_ids) override {
+    removed_ids_ = request_ids;
+  }
+  std::vector<int64_t> removed_ids() const { return removed_ids_; }
+
+  std::map<GURL, std::vector<int64_t>>* live_auto_fetch_requests() {
+    return &live_auto_fetch_requests_;
+  }
+
+  using AutoFetchPageLoadWatcher::HandlePageNavigation;
+  using AutoFetchPageLoadWatcher::ObserverInitialize;
+
+ private:
+  std::vector<int64_t> removed_ids_;
+};
+
+// Tests AutoFetchPageLoadWatcher in a realistic way by simulating navigations.
+class AutoFetchPageLoadWatcherNavigationTest
+    : public ChromeRenderViewHostTestHarness {
+ public:
+  AutoFetchPageLoadWatcherNavigationTest() = default;
+  ~AutoFetchPageLoadWatcherNavigationTest() override = default;
+
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    RequestCoordinatorFactory::GetInstance()->SetTestingFactoryAndUse(
+        profile(), request_coordinator_taco_.FactoryFunction());
+
+    AutoFetchPageLoadWatcher::CreateForWebContents(web_contents());
+  }
+
+  std::unique_ptr<NavigationSimulator> CreateNavigation(const GURL& url) {
+    return NavigationSimulator::CreateRendererInitiated(url, main_rfh());
+  }
+
+  RequestCoordinator* request_coordinator() {
+    return request_coordinator_taco_.request_coordinator();
+  }
+
+  void AddAutoSavePageRequest() {
+    RequestCoordinator::SavePageLaterParams params;
+    params.url = TestURL();
+    params.client_id = ClientId(kAutoAsyncNamespace, "request1");
+    request_coordinator()->SavePageLater(params, base::DoNothing());
+  }
+
+  std::vector<GURL> RequestsInQueue() {
+    std::vector<GURL> request_urls;
+    request_coordinator()->GetAllRequests(base::BindLambdaForTesting(
+        [&](std::vector<std::unique_ptr<SavePageRequest>> requests) {
+          for (const auto& request : requests) {
+            request_urls.push_back(request->url());
+          }
+        }));
+    RunUntilIdle();
+    return request_urls;
+  }
+
+  void RunUntilIdle() { thread_bundle()->RunUntilIdle(); }
+
+ private:
+  RequestCoordinatorStubTaco request_coordinator_taco_;
+
+  DISALLOW_COPY_AND_ASSIGN(AutoFetchPageLoadWatcherNavigationTest);
+};
+
+// Navigation results in an error page, and has no effect on
+// AutoFetchPageLoadWatcher.
+TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateToErrorPage) {
+  AddAutoSavePageRequest();
+  RunUntilIdle();
+
+  std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL());
+  simulator->Start();
+  simulator->Fail(net::ERR_TIMED_OUT);
+  simulator->CommitErrorPage();
+
+  std::vector<GURL> expected_requests{TestURL()};
+  EXPECT_EQ(expected_requests, RequestsInQueue());
+}
+
+// Successful navigation results in cancellation of request.
+TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateAndCancel) {
+  AddAutoSavePageRequest();
+  RunUntilIdle();
+
+  std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL());
+  simulator->Start();
+  simulator->Commit();
+
+  std::vector<GURL> expected_requests{};
+  EXPECT_EQ(expected_requests, RequestsInQueue());
+}
+
+TEST_F(AutoFetchPageLoadWatcherNavigationTest, NavigateToDifferentURL) {
+  AddAutoSavePageRequest();
+  RunUntilIdle();
+
+  std::unique_ptr<NavigationSimulator> simulator =
+      CreateNavigation(GURL("http://www.different.com"));
+  simulator->Start();
+  simulator->Commit();
+
+  std::vector<GURL> expected_requests{TestURL()};
+  EXPECT_EQ(expected_requests, RequestsInQueue());
+}
+
+TEST_F(AutoFetchPageLoadWatcherNavigationTest, RedirectToAndCancel) {
+  AddAutoSavePageRequest();
+  RunUntilIdle();
+
+  std::unique_ptr<NavigationSimulator> simulator =
+      CreateNavigation(GURL("http://different.com"));
+  simulator->Start();
+  simulator->Redirect(TestURL());
+  simulator->Commit();
+
+  std::vector<GURL> expected_requests{};
+  EXPECT_EQ(expected_requests, RequestsInQueue());
+}
+
+TEST_F(AutoFetchPageLoadWatcherNavigationTest, RedirectFromAndCancel) {
+  AddAutoSavePageRequest();
+  RunUntilIdle();
+
+  std::unique_ptr<NavigationSimulator> simulator = CreateNavigation(TestURL());
+
+  simulator->Start();
+  simulator->Redirect(GURL("http://different.com"));
+  simulator->Commit();
+
+  std::vector<GURL> expected_requests{};
+  EXPECT_EQ(expected_requests, RequestsInQueue());
+}
+
+// Tests some details of AutoFetchPageLoadWatcher
+class AutoFetchPageLoadWatcherTest : public testing::Test {
+ public:
+  AutoFetchPageLoadWatcherTest() {}
+  ~AutoFetchPageLoadWatcherTest() override {}
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    taco_.CreateRequestCoordinator();
+  }
+
+  RequestCoordinator* request_coordinator() {
+    return taco_.request_coordinator();
+  }
+
+ private:
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_ =
+      base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  base::ThreadTaskRunnerHandle handle_{task_runner_};
+
+  RequestCoordinatorStubTaco taco_;
+};
+
+// Simulate navigation to a URL prior to ObserverInitialize. Verify the
+// RemoveRequests is called.
+TEST_F(AutoFetchPageLoadWatcherTest, NavigateBeforeObserverInitialize) {
+  TestAutoFetchPageLoadWatcher tab_helper(request_coordinator());
+  tab_helper.HandlePageNavigation(TestURL());
+  std::vector<std::unique_ptr<SavePageRequest>> all_requests;
+  all_requests.push_back(std::make_unique<SavePageRequest>(TestRequest(1)));
+  all_requests.push_back(std::make_unique<SavePageRequest>(
+      TestRequest(2, GURL("http://different.com"))));
+  tab_helper.ObserverInitialize(std::move(all_requests));
+
+  std::vector<int64_t> expected_requests{1};
+  EXPECT_EQ(expected_requests, tab_helper.removed_ids());
+}
+
+TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedNoRequest) {
+  TestAutoFetchPageLoadWatcher tab_helper(request_coordinator());
+  tab_helper.ObserverInitialize({});
+
+  tab_helper.OnCompleted(TestRequest(1),
+                         RequestNotifier::BackgroundSavePageResult());
+
+  // Nothing happens, just verify there is no crash.
+  SUCCEED();
+}
+
+TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedOneRequestWithURL) {
+  TestAutoFetchPageLoadWatcher tab_helper(request_coordinator());
+  tab_helper.ObserverInitialize({});
+  std::map<GURL, std::vector<int64_t>>* requests =
+      tab_helper.live_auto_fetch_requests();
+
+  tab_helper.OnAdded(TestRequest(1));
+  ASSERT_EQ(1ul, requests->count(TestURL()));
+
+  tab_helper.OnCompleted(TestRequest(1),
+                         RequestNotifier::BackgroundSavePageResult());
+
+  EXPECT_EQ(0ul, requests->count(TestURL()));
+}
+
+// Verify multiple requests with the same URL are handled as expected.
+TEST_F(AutoFetchPageLoadWatcherTest, OnCompletedMultipleRequestsWithURL) {
+  TestAutoFetchPageLoadWatcher tab_helper(request_coordinator());
+  tab_helper.ObserverInitialize({});
+
+  // Three requests with the same URL.
+  tab_helper.OnAdded(TestRequest(1));
+  tab_helper.OnAdded(TestRequest(2));
+  tab_helper.OnAdded(TestRequest(3));
+
+  // Only one is completed.
+  tab_helper.OnCompleted(TestRequest(2),
+                         RequestNotifier::BackgroundSavePageResult());
+
+  std::vector<int64_t> expected_requests = {1, 3};
+  EXPECT_EQ(expected_requests,
+            (*tab_helper.live_auto_fetch_requests())[TestURL()]);
+}
+
+}  // namespace
+}  // namespace offline_pages
diff --git a/chrome/browser/offline_pages/offline_page_auto_fetcher_service.cc b/chrome/browser/offline_pages/offline_page_auto_fetcher_service.cc
index 74ab8f37..87d607e9 100644
--- a/chrome/browser/offline_pages/offline_page_auto_fetcher_service.cc
+++ b/chrome/browser/offline_pages/offline_page_auto_fetcher_service.cc
@@ -71,7 +71,8 @@
 
 OfflinePageAutoFetcherService::OfflinePageAutoFetcherService(
     RequestCoordinator* request_coordinator)
-    : request_coordinator_(request_coordinator) {}
+    : page_load_watcher_(request_coordinator),
+      request_coordinator_(request_coordinator) {}
 OfflinePageAutoFetcherService::~OfflinePageAutoFetcherService() {}
 
 void OfflinePageAutoFetcherService::TrySchedule(bool user_requested,
diff --git a/chrome/browser/offline_pages/offline_page_auto_fetcher_service.h b/chrome/browser/offline_pages/offline_page_auto_fetcher_service.h
index 7474a22..2904452 100644
--- a/chrome/browser/offline_pages/offline_page_auto_fetcher_service.h
+++ b/chrome/browser/offline_pages/offline_page_auto_fetcher_service.h
@@ -6,10 +6,12 @@
 #define CHROME_BROWSER_OFFLINE_PAGES_OFFLINE_PAGE_AUTO_FETCHER_SERVICE_H_
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/offline_pages/auto_fetch_page_load_watcher.h"
 #include "chrome/common/offline_page_auto_fetcher.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/offline_pages/core/background/request_queue_results.h"
@@ -55,6 +57,8 @@
       RequestCoordinator* request_coordinator);
   ~OfflinePageAutoFetcherService() override;
 
+  AutoFetchPageLoadWatcher* page_load_watcher() { return &page_load_watcher_; }
+
   // Auto fetching interface. Schedules and cancels fetch requests.
 
   void TrySchedule(bool user_requested,
@@ -114,6 +118,7 @@
       std::vector<std::unique_ptr<SavePageRequest>> requests);
   void CancelScheduleStep3(TaskToken token, const MultipleItemStatuses&);
 
+  AutoFetchPageLoadWatcher page_load_watcher_;
   RequestCoordinator* request_coordinator_;
   // TODO(harringtond): Pull out task management into another class, or use
   // offline_pages::TaskQueue.
diff --git a/chrome/browser/offline_pages/offline_page_auto_fetcher_service_factory.cc b/chrome/browser/offline_pages/offline_page_auto_fetcher_service_factory.cc
index 94ca367d..5ad9b57d 100644
--- a/chrome/browser/offline_pages/offline_page_auto_fetcher_service_factory.cc
+++ b/chrome/browser/offline_pages/offline_page_auto_fetcher_service_factory.cc
@@ -21,8 +21,11 @@
 OfflinePageAutoFetcherService*
 OfflinePageAutoFetcherServiceFactory::GetForBrowserContext(
     content::BrowserContext* context) {
-  return static_cast<OfflinePageAutoFetcherService*>(
-      GetInstance()->GetServiceForBrowserContext(context, true));
+  KeyedService* service =
+      GetInstance()->GetServiceForBrowserContext(context, true);
+  if (!service)
+    return nullptr;
+  return static_cast<OfflinePageAutoFetcherService*>(service);
 }
 
 OfflinePageAutoFetcherServiceFactory::OfflinePageAutoFetcherServiceFactory()
diff --git a/chrome/browser/offline_pages/offline_page_tab_helper.h b/chrome/browser/offline_pages/offline_page_tab_helper.h
index 6b96422..8ca297fe 100644
--- a/chrome/browser/offline_pages/offline_page_tab_helper.h
+++ b/chrome/browser/offline_pages/offline_page_tab_helper.h
@@ -204,7 +204,7 @@
 
   bool reloading_url_on_net_error_ = false;
 
-  // Service, overlives this object.
+  // Service, outlives this object.
   PrefetchService* prefetch_service_ = nullptr;
 
   // Table of OfflinePages policies.
diff --git a/chrome/browser/policy/browser_dm_token_storage.cc b/chrome/browser/policy/browser_dm_token_storage.cc
index 06acff9..6fa2d2d 100644
--- a/chrome/browser/policy/browser_dm_token_storage.cc
+++ b/chrome/browser/policy/browser_dm_token_storage.cc
@@ -83,6 +83,13 @@
     std::move(store_callback_).Run(success);
 }
 
+bool BrowserDMTokenStorage::ShouldDisplayErrorMessageOnFailure() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  InitIfNeeded();
+  return should_display_error_message_on_failure_;
+}
+
 void BrowserDMTokenStorage::ScheduleUnusedPolicyDirectoryDeletion() {
   // TODO(crbug.com/883869): Add a UMA metrics to track the deletion progress.
   content::BrowserThread::PostAfterStartupTask(
@@ -113,6 +120,8 @@
 
   dm_token_ = InitDMToken();
   DVLOG(1) << "DM Token = " << dm_token_;
+
+  should_display_error_message_on_failure_ = InitEnrollmentErrorOption();
 }
 
 void BrowserDMTokenStorage::DeletePolicyDirectory() {}
diff --git a/chrome/browser/policy/browser_dm_token_storage.h b/chrome/browser/policy/browser_dm_token_storage.h
index b090387..2347514c 100644
--- a/chrome/browser/policy/browser_dm_token_storage.h
+++ b/chrome/browser/policy/browser_dm_token_storage.h
@@ -49,6 +49,10 @@
   // invoked.
   void OnDMTokenStored(bool success);
 
+  // Return true if we display error message dialog when enrollment process
+  // fails.
+  virtual bool ShouldDisplayErrorMessageOnFailure();
+
   // Set the mock BrowserDMTokenStorage for testing. The caller owns the
   // instance of the storage.
   static void SetForTesting(BrowserDMTokenStorage* storage) {
@@ -82,6 +86,9 @@
   // Gets the DM token and stores it in |dm_token_|. This implementation is
   // platform dependant.
   virtual std::string InitDMToken() = 0;
+  // Gets the boolean value that determines if error message will be displayed
+  // when enrollment fails.
+  virtual bool InitEnrollmentErrorOption() = 0;
   // Saves the DM token. This implementation is platform dependant.
   virtual void SaveDMToken(const std::string& token) = 0;
 
@@ -96,6 +103,7 @@
   std::string client_id_;
   std::string enrollment_token_;
   std::string dm_token_;
+  bool should_display_error_message_on_failure_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/chrome/browser/policy/browser_dm_token_storage_linux.cc b/chrome/browser/policy/browser_dm_token_storage_linux.cc
index a21bf55..ceebd05 100644
--- a/chrome/browser/policy/browser_dm_token_storage_linux.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_linux.cc
@@ -134,6 +134,11 @@
   return token;
 }
 
+bool BrowserDMTokenStorageLinux::InitEnrollmentErrorOption() {
+  // TODO(crbug/904983): Load the policy value for this option.
+  return true;
+}
+
 void BrowserDMTokenStorageLinux::SaveDMToken(const std::string& token) {
   std::string client_id = RetrieveClientId();
   base::PostTaskWithTraitsAndReplyWithResult(
diff --git a/chrome/browser/policy/browser_dm_token_storage_linux.h b/chrome/browser/policy/browser_dm_token_storage_linux.h
index c4d7702..f7fc3a049 100644
--- a/chrome/browser/policy/browser_dm_token_storage_linux.h
+++ b/chrome/browser/policy/browser_dm_token_storage_linux.h
@@ -31,6 +31,7 @@
   std::string InitClientId() override;
   std::string InitEnrollmentToken() override;
   std::string InitDMToken() override;
+  bool InitEnrollmentErrorOption() override;
   void SaveDMToken(const std::string& token) override;
   void DeletePolicyDirectory() override;
 
diff --git a/chrome/browser/policy/browser_dm_token_storage_mac.h b/chrome/browser/policy/browser_dm_token_storage_mac.h
index c0ba3c4..5c9dda4 100644
--- a/chrome/browser/policy/browser_dm_token_storage_mac.h
+++ b/chrome/browser/policy/browser_dm_token_storage_mac.h
@@ -31,6 +31,7 @@
   std::string InitClientId() override;
   std::string InitEnrollmentToken() override;
   std::string InitDMToken() override;
+  bool InitEnrollmentErrorOption() override;
   void SaveDMToken(const std::string& token) override;
   void DeletePolicyDirectory() override;
 
diff --git a/chrome/browser/policy/browser_dm_token_storage_mac.mm b/chrome/browser/policy/browser_dm_token_storage_mac.mm
index d7f8e37..34a1951 100644
--- a/chrome/browser/policy/browser_dm_token_storage_mac.mm
+++ b/chrome/browser/policy/browser_dm_token_storage_mac.mm
@@ -181,6 +181,11 @@
   return token;
 }
 
+bool BrowserDMTokenStorageMac::InitEnrollmentErrorOption() {
+  // TODO(crbug/904983): Load the policy value for this option.
+  return true;
+}
+
 void BrowserDMTokenStorageMac::SaveDMToken(const std::string& token) {
   std::string client_id = RetrieveClientId();
   base::PostTaskWithTraitsAndReplyWithResult(
diff --git a/chrome/browser/policy/browser_dm_token_storage_unittest.cc b/chrome/browser/policy/browser_dm_token_storage_unittest.cc
index ba32c995..0feabb3 100644
--- a/chrome/browser/policy/browser_dm_token_storage_unittest.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_unittest.cc
@@ -34,12 +34,14 @@
     set_test_client_id(kClientId1);
     set_test_enrollment_token(kEnrollmentToken1);
     set_test_dm_token(kDMToken1);
+    set_test_error_option(false);
   }
 
   // BrowserDMTokenStorage override
   std::string InitClientId() override { return test_client_id_; }
   std::string InitEnrollmentToken() override { return test_enrollment_token_; }
   std::string InitDMToken() override { return test_dm_token_; }
+  bool InitEnrollmentErrorOption() override { return test_error_option_; }
 
   void SaveDMToken(const std::string& dm_token) override { NOTREACHED(); }
 
@@ -52,11 +54,15 @@
   void set_test_dm_token(std::string test_dm_token) {
     test_dm_token_ = test_dm_token;
   }
+  void set_test_error_option(bool error_option) {
+    test_error_option_ = error_option;
+  }
 
  private:
   std::string test_client_id_;
   std::string test_enrollment_token_;
   std::string test_dm_token_;
+  bool test_error_option_;
 };
 
 class BrowserDMTokenStorageTest : public testing::Test {
@@ -93,4 +99,14 @@
   EXPECT_EQ(kDMToken1, storage.RetrieveDMToken());
 }
 
+TEST_F(BrowserDMTokenStorageTest, ShouldDisplayErrorMessageOnFailure) {
+  MockBrowserDMTokenStorage storage;
+  EXPECT_FALSE(storage.ShouldDisplayErrorMessageOnFailure());
+
+  // The error option should be cached in memory and not read from the system
+  // again.
+  storage.set_test_error_option(true);
+  EXPECT_FALSE(storage.ShouldDisplayErrorMessageOnFailure());
+}
+
 }  // namespace policy
diff --git a/chrome/browser/policy/browser_dm_token_storage_win.cc b/chrome/browser/policy/browser_dm_token_storage_win.cc
index 00083133..5127a046 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win.cc
+++ b/chrome/browser/policy/browser_dm_token_storage_win.cc
@@ -190,6 +190,11 @@
   return dm_token;
 }
 
+bool BrowserDMTokenStorageWin::InitEnrollmentErrorOption() {
+  // TODO(crbug/904983): Load the policy value for this option.
+  return true;
+}
+
 void BrowserDMTokenStorageWin::SaveDMToken(const std::string& token) {
   base::PostTaskAndReplyWithResult(
       com_sta_task_runner_.get(), FROM_HERE,
diff --git a/chrome/browser/policy/browser_dm_token_storage_win.h b/chrome/browser/policy/browser_dm_token_storage_win.h
index 57a82ff..cc5d9e0 100644
--- a/chrome/browser/policy/browser_dm_token_storage_win.h
+++ b/chrome/browser/policy/browser_dm_token_storage_win.h
@@ -31,6 +31,7 @@
   std::string InitClientId() override;
   std::string InitEnrollmentToken() override;
   std::string InitDMToken() override;
+  bool InitEnrollmentErrorOption() override;
   void SaveDMToken(const std::string& token) override;
 
   scoped_refptr<base::SingleThreadTaskRunner> com_sta_task_runner_;
diff --git a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
index addf04e5..9deea9a0 100644
--- a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
@@ -116,6 +116,7 @@
     std::move(callback).Run(storage_enabled_);
   }
   std::string RetrieveDMToken() override { return dm_token_; }
+  bool ShouldDisplayErrorMessageOnFailure() override { return true; }
 
   void SetEnrollmentToken(const std::string& enrollment_token) {
     enrollment_token_ = enrollment_token;
@@ -135,6 +136,11 @@
     NOTREACHED();
     return std::string();
   }
+  bool InitEnrollmentErrorOption() override {
+    NOTREACHED();
+    return true;
+  }
+
   void SaveDMToken(const std::string& dm_token) override { NOTREACHED(); }
 
   void EnableStorage(bool storage_enabled) {
diff --git a/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher_unittest.cc b/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher_unittest.cc
index 50728e5..52af0ae8 100644
--- a/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher_unittest.cc
+++ b/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher_unittest.cc
@@ -38,6 +38,7 @@
   std::string RetrieveDMToken() override { return dm_token_; }
   std::string RetrieveEnrollmentToken() override { return enrollment_token_; }
   std::string RetrieveClientId() override { return kClientId; }
+  bool ShouldDisplayErrorMessageOnFailure() override { return true; }
 
   std::string InitClientId() override {
     NOTREACHED();
@@ -51,6 +52,10 @@
     NOTREACHED();
     return std::string();
   }
+  bool InitEnrollmentErrorOption() override {
+    NOTREACHED();
+    return true;
+  }
 
   void SaveDMToken(const std::string& dm_token) override { NOTREACHED(); }
 
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index e28e22c..888962b 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -21,6 +21,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
@@ -4166,8 +4167,8 @@
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(PolicyWebStoreIconTest);
   base::test::ScopedFeatureList scoped_feature_list;
+  DISALLOW_COPY_AND_ASSIGN(PolicyWebStoreIconTest);
 };
 
 IN_PROC_BROWSER_TEST_F(PolicyWebStoreIconTest, AppsWebStoreIconHidden) {
@@ -4249,8 +4250,8 @@
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(PolicyWebStoreIconHiddenTest);
   base::test::ScopedFeatureList scoped_feature_list;
+  DISALLOW_COPY_AND_ASSIGN(PolicyWebStoreIconHiddenTest);
 };
 
 IN_PROC_BROWSER_TEST_F(PolicyWebStoreIconHiddenTest, NTPWebStoreIconHidden) {
@@ -5444,10 +5445,31 @@
       << post_interceptor_->GetRequestsAsString();
   ASSERT_EQ(1, post_interceptor_->GetCount())
       << post_interceptor_->GetRequestsAsString();
-  EXPECT_NE(std::string::npos,
-            post_interceptor_->GetRequestBody(0).find(base::StringPrintf(
-                "<updatecheck%s/>",
-                update_disabled ? " updatedisabled=\"true\"" : "")));
+
+  const auto& request = post_interceptor_->GetRequestBody(0);
+
+  // Handle XML and JSON protocols.
+  if (base::StartsWith(request, "<?xml", base::CompareCase::SENSITIVE)) {
+    EXPECT_NE(std::string::npos,
+              request.find(base::StringPrintf(
+                  "<updatecheck%s/>",
+                  update_disabled ? " updatedisabled=\"true\"" : "")));
+  } else if (base::StartsWith(request, R"({"request":{)",
+                              base::CompareCase::SENSITIVE)) {
+    const auto root = base::JSONReader().Read(request);
+    ASSERT_TRUE(root);
+    const auto* update_check =
+        root->FindKey("request")->FindKey("app")->GetList()[0].FindKey(
+            "updatecheck");
+    ASSERT_TRUE(update_check);
+    if (update_disabled) {
+      EXPECT_EQ(true, update_check->FindKey("updatedisabled")->GetBool());
+    } else {
+      EXPECT_FALSE(update_check->FindKey("updatedisabled"));
+    }
+  } else {
+    NOTREACHED();
+  }
 }
 
 void ComponentUpdaterPolicyTest::DefaultPolicy_GroupPolicySupported() {
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc b/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
index 1fb28fdb..1199586da 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
@@ -23,11 +23,12 @@
   struct TestCase {
     std::string previews_host;
     std::string original_url;
-    std::string previews_url;
+    std::string expected_previews_url;
     std::string experiment;
   };
   const TestCase kTestCases[]{
-      // Use https://play.golang.org/p/HUM2HxmUTOW to compute |previews_url|.
+      // Use https://play.golang.org/p/HUM2HxmUTOW to compute
+      // |expected_previews_url|.
       {
           "https://previews.host.com",
           "https://original.host.com/path/path/path?query=yes",
@@ -86,6 +87,12 @@
           "&x=enable_HTCPCP",
           "enable_HTCPCP",
       },
+      {
+          "https://previews.host.com", "https://[::1]:12345",
+          "https://2ikmbopbfxagkb7uer2vgfxmbzu2vw4qq3d3ixe3h2hfhgcabvua."
+          "previews.host.com/p?u=https%3A%2F%2F%5B%3A%3A1%5D%3A12345%2F",
+          "",
+      },
   };
 
   for (const TestCase& test_case : kTestCases) {
@@ -97,6 +104,6 @@
 
     EXPECT_EQ(PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(
                   GURL(test_case.original_url)),
-              GURL(test_case.previews_url));
+              GURL(test_case.expected_previews_url));
   }
 }
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 73169a5c..739a0b5f 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -123,6 +123,8 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/dom_storage_context.h"
 #include "content/public/browser/notification_service.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_type.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/url_data_source.h"
@@ -326,6 +328,31 @@
 }
 #endif
 
+void CheckDomainReliablityUploadAllowedOnUIThread(
+    Profile* profile,
+    const GURL& origin,
+    base::OnceCallback<void(bool)> callback) {
+  // Profile is safe since ProfileImpl always outlives IO thread.
+  content::PermissionController* permission_controller =
+      content::BrowserContext::GetPermissionController(profile);
+  DCHECK(permission_controller);
+  bool allowed = permission_controller->GetPermissionStatus(
+                     content::PermissionType::BACKGROUND_SYNC, origin,
+                     origin) == blink::mojom::PermissionStatus::GRANTED;
+  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
+                           base::BindOnce(std::move(callback), allowed));
+}
+
+void CheckDomainReliablityUploadAllowedOnIOThread(
+    Profile* profile,
+    const GURL& origin,
+    base::OnceCallback<void(bool)> callback) {
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(CheckDomainReliablityUploadAllowedOnUIThread, profile,
+                     origin, std::move(callback)));
+}
+
 }  // namespace
 
 // static
@@ -1476,7 +1503,8 @@
 
   return service->CreateMonitor(
       base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}),
-      base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO}));
+      base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO}),
+      base::BindRepeating(CheckDomainReliablityUploadAllowedOnIOThread, this));
 }
 
 std::unique_ptr<service_manager::Service> ProfileImpl::CreateIdentityService() {
diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc
index e6d3848..cf694ac 100644
--- a/chrome/browser/profiles/profile_impl_io_data.cc
+++ b/chrome/browser/profiles/profile_impl_io_data.cc
@@ -174,7 +174,8 @@
 
   io_data_->InitializeMetricsEnabledStateOnUIThread();
   if (io_data_->lazy_params_->domain_reliability_monitor)
-    io_data_->lazy_params_->domain_reliability_monitor->MoveToNetworkThread();
+    io_data_->lazy_params_->domain_reliability_monitor
+        ->InitializeOnNetworkThread();
 
   io_data_->set_data_reduction_proxy_io_data(
       CreateDataReductionProxyChromeIOData(
diff --git a/chrome/browser/resources/chromeos/arc_support/recommend_app_list_view.js b/chrome/browser/resources/chromeos/arc_support/recommend_app_list_view.js
index 8bb839f0..ec7bf67 100644
--- a/chrome/browser/resources/chromeos/arc_support/recommend_app_list_view.js
+++ b/chrome/browser/resources/chromeos/arc_support/recommend_app_list_view.js
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+let appWindow;
+let appOrigin;
+
 function generateContents(appIcon, appTitle, appPackageName) {
   const doc = document;
   const recommendAppsContainer = doc.getElementById('recommend-apps-container');
@@ -97,6 +100,8 @@
 function toggleCheckStatus_(e) {
   const item = e.currentTarget.parentNode;
   item.classList.toggle('checked');
+
+  sendNumberOfSelectedApps();
 }
 
 function getSelectedPackages() {
@@ -139,3 +144,22 @@
   return e.keyCode === 13   // Enter
       || e.keyCode === 32;  // Space
 }
+
+/**
+ * Send the number of selected apps back to the embedding page.
+ */
+function sendNumberOfSelectedApps() {
+  if (appWindow && appOrigin) {
+    const checkedItems = document.querySelectorAll('.checked');
+    appWindow.postMessage(
+        {type: 'NUM_OF_SELECTED_APPS', numOfSelected: checkedItems.length},
+        appOrigin);
+  }
+}
+
+function onMessage_(e) {
+  appWindow = e.source;
+  appOrigin = e.origin;
+}
+
+window.addEventListener('message', onMessage_);
diff --git a/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js b/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
index a671637..d144464 100644
--- a/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
+++ b/chrome/browser/resources/chromeos/chromevox/testing/callback_helper.js
@@ -48,6 +48,6 @@
  * @private
  */
 CallbackHelper.testDone_ = this.testDone;
-// Remove testDone for public use since direclty using it conflicts with
+// Remove testDone for public use since directly using it conflicts with
 // this callback helper.
 delete this.testDone;
diff --git a/chrome/browser/resources/chromeos/login/screen_recommend_apps.js b/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
index 7560848..9e1f9a2 100644
--- a/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
+++ b/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
@@ -59,6 +59,7 @@
      */
     ensureInitialized_: function() {
       $('recommend-apps-screen').screen = this;
+      window.addEventListener('message', this.onMessage);
     },
 
     /**
@@ -77,7 +78,7 @@
     },
 
     setWebview: function(contents) {
-      var appListView = this.getElement_('app-list-view');
+      const appListView = this.getElement_('app-list-view');
       appListView.src =
           'data:text/html;charset=utf-8,' + encodeURIComponent(contents);
     },
@@ -91,21 +92,26 @@
       // Hide the loading throbber and show the recommend app list.
       this.setThrobberVisible(false);
 
-      var appListView = this.getElement_('app-list-view');
-      var subtitle = this.getElement_('subtitle');
+      const appListView = this.getElement_('app-list-view');
+      const subtitle = this.getElement_('subtitle');
       subtitle.innerText = loadTimeData.getStringF(
           'recommendAppsScreenDescription', appList.length);
       appListView.addEventListener('contentload', () => {
+        appListView.contentWindow.postMessage('initialMessage', '*');
+
         appListView.executeScript({file: 'recommend_app_list_view.js'}, () => {
           appList.forEach(function(app, index) {
-            var generateItemScript = 'generateContents("' + app.icon + '", "' +
+            let generateItemScript = 'generateContents("' + app.icon + '", "' +
                 app.name + '", "' + app.package_name + '");';
-            var generateContents = {code: generateItemScript};
+            const generateContents = {code: generateItemScript};
             appListView.executeScript(generateContents);
           });
-          var addScrollShadowEffectScript = 'addScrollShadowEffect();';
+          const addScrollShadowEffectScript = 'addScrollShadowEffect();';
           appListView.executeScript({code: addScrollShadowEffectScript});
 
+          const getNumOfSelectedAppsScript = 'sendNumberOfSelectedApps();';
+          appListView.executeScript({code: getNumOfSelectedAppsScript});
+
           this.onGenerateContents();
         });
       });
@@ -132,12 +138,15 @@
      * Handles Install button click.
      */
     onInstall: function() {
-      var appListView = this.getElement_('app-list-view');
-      appListView.executeScript(
-          {code: 'getSelectedPackages();'}, function(result) {
-            console.log(result[0]);
-            chrome.send('recommendAppsInstall', result[0]);
-          });
+      // Only start installation if the button is not disabled.
+      if (!this.getElement_('recommend-apps-install-button').disabled) {
+        const appListView = this.getElement_('app-list-view');
+        appListView.executeScript(
+            {code: 'getSelectedPackages();'}, function(result) {
+              console.log(result[0]);
+              chrome.send('recommendAppsInstall', result[0]);
+            });
+      }
     },
 
     /**
@@ -153,6 +162,18 @@
     },
 
     /**
+     * Handles the message sent from the WebView.
+     */
+    onMessage: function(event) {
+      if (event.data.type && (event.data.type === 'NUM_OF_SELECTED_APPS')) {
+        const numOfSelected = event.data.numOfSelected;
+        $('recommend-apps-screen')
+            .getElement('recommend-apps-install-button')
+            .disabled = (numOfSelected === 0);
+      }
+    },
+
+    /**
      * This is called to show/hide the loading UI.
      * @param {boolean} visible whether to show loading UI.
      */
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/custom_backgrounds.css
index 9cbc5c78..014e137 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.css
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.css
@@ -485,9 +485,10 @@
 .selected-border {
   border: 2px solid rgba(26, 115, 232, .4);
   border-radius: 4px;
-  height: 113px;
+  box-sizing: border-box;
+  height: 100%;
   position: relative;
-  width: 152px;
+  width: 100%;
 }
 
 .selected-circle {
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.css b/chrome/browser/resources/local_ntp/most_visited_single.css
index 97f38461..455565f 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.css
+++ b/chrome/browser/resources/local_ntp/most_visited_single.css
@@ -80,7 +80,8 @@
   user-select: none;
 }
 
-.md #mv-tiles {
+.md #mv-tiles,
+.md .mv-tiles-old {
   display: flex;
   flex-wrap: wrap;
   height: auto;
@@ -91,10 +92,14 @@
    * values in local_ntp.css. */
   max-width: calc(var(--md-tile-width) * var(--md-max-tiles-row));
   position: static;
+  transition-duration: 300ms;
 }
 
 .md .mv-tiles-old {
-  display: none;
+  left: 0;
+  margin: auto;
+  position: absolute;
+  right: 0;
 }
 
 .mv-tile,
diff --git a/chrome/browser/resources/settings/passwords_and_forms_page/passwords_shared_css.html b/chrome/browser/resources/settings/passwords_and_forms_page/passwords_shared_css.html
index 7c97ad9..3dcec5c 100644
--- a/chrome/browser/resources/settings/passwords_and_forms_page/passwords_shared_css.html
+++ b/chrome/browser/resources/settings/passwords_and_forms_page/passwords_shared_css.html
@@ -12,6 +12,7 @@
       }
 
       .website-column {
+        align-items: center;
         display: flex;
         flex: 1;
       }
diff --git a/chrome/browser/resources_util.cc b/chrome/browser/resources_util.cc
index d74bc7f..e2bbf23a9 100644
--- a/chrome/browser/resources_util.cc
+++ b/chrome/browser/resources_util.cc
@@ -9,7 +9,7 @@
 #include <utility>
 
 #include "base/containers/flat_map.h"
-#include "base/lazy_instance.h"
+#include "base/no_destructor.h"
 #include "build/build_config.h"
 #include "chrome/grit/theme_resources_map.h"
 #include "components/grit/components_scaled_resources_map.h"
@@ -22,15 +22,22 @@
 namespace {
 
 // A wrapper class that holds a map between resource strings and resource
-// ids.  This is done so we can use base::LazyInstance which takes care of
+// ids.  This is done so we can use base::NoDestructor which takes care of
 // thread safety in initializing the map for us.
 class ThemeMap {
  public:
-  typedef base::flat_map<std::string, int> StringIntMap;
+  using StringIntMap = base::flat_map<std::string, int>;
 
   ThemeMap() {
+    size_t storage_size =
+        kComponentsScaledResourcesSize + kThemeResourcesSize + kUiResourcesSize;
+#if defined(OS_CHROMEOS)
+    storage_size += kUiChromeosResourcesSize;
+#endif
+
     // Construct in one-shot from a moved vector.
     std::vector<StringIntMap::value_type> storage;
+    storage.reserve(storage_size);
 
     for (size_t i = 0; i < kComponentsScaledResourcesSize; ++i) {
       storage.emplace_back(kComponentsScaledResources[i].name,
@@ -52,22 +59,22 @@
     id_map_ = StringIntMap(std::move(storage), base::KEEP_FIRST_OF_DUPES);
   }
 
-  int GetId(const std::string& resource_name) {
+  int GetId(const std::string& resource_name) const {
     auto it = id_map_.find(resource_name);
-    if (it == id_map_.end())
-      return -1;
-    return it->second;
+    return it != id_map_.end() ? it->second : -1;
   }
 
  private:
   StringIntMap id_map_;
 };
 
-static base::LazyInstance<ThemeMap>::DestructorAtExit g_theme_ids =
-    LAZY_INSTANCE_INITIALIZER;
+ThemeMap& GetThemeIdsMap() {
+  static base::NoDestructor<ThemeMap> s;
+  return *s;
+}
 
 }  // namespace
 
 int ResourcesUtil::GetThemeResourceId(const std::string& resource_name) {
-  return g_theme_ids.Get().GetId(resource_name);
+  return GetThemeIdsMap().GetId(resource_name);
 }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 3faf0698..e5857a3 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -118,7 +118,6 @@
     "find_bar/find_notification_details.h",
     "find_bar/find_tab_helper.cc",
     "find_bar/find_tab_helper.h",
-    "forced_reauthentication_dialog.h",
     "interventions/framebust_block_message_delegate.cc",
     "interventions/framebust_block_message_delegate.h",
     "interventions/intervention_delegate.h",
@@ -2950,8 +2949,6 @@
         "views/outdated_upgrade_bubble_view.h",
         "views/policy/enterprise_startup_dialog_view.cc",
         "views/policy/enterprise_startup_dialog_view.h",
-        "views/profiles/forced_reauthentication_dialog_view.cc",
-        "views/profiles/forced_reauthentication_dialog_view.h",
         "views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.cc",
         "views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.h",
         "views/relaunch_notification/relaunch_recommended_bubble_view.cc",
diff --git a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
index b72d66d..4b1dad2 100644
--- a/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_browsertest.cc
@@ -178,16 +178,8 @@
 
 #endif
 
-// Flaky on Linux: http://crbug.com/504869.
-#if defined(OS_LINUX)
-#define MAYBE_HideStarOnNonbookmarkedInterstitial \
-  DISABLED_HideStarOnNonbookmarkedInterstitial
-#else
-#define MAYBE_HideStarOnNonbookmarkedInterstitial \
-  HideStarOnNonbookmarkedInterstitial
-#endif
 IN_PROC_BROWSER_TEST_F(BookmarkBrowsertest,
-                       MAYBE_HideStarOnNonbookmarkedInterstitial) {
+                       HideStarOnNonbookmarkedInterstitial) {
   // Start an HTTPS server with a certificate error.
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME);
diff --git a/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_views_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_views_cocoa_browsertest.mm
index fb212c9..249663a 100644
--- a/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_views_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/permission_bubble/permission_bubble_views_cocoa_browsertest.mm
@@ -37,13 +37,7 @@
 }
 
 bool HasVisibleLocationBarForBrowser(Browser* browser) {
-  if (!browser->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR))
-    return false;
-
-  if (!browser->exclusive_access_manager()->context()->IsFullscreen())
-    return true;
-
-  return false;
+  return browser->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
 }
 
 }  // namespace
@@ -81,10 +75,9 @@
   EXPECT_FALSE(HasVisibleLocationBarForBrowser(app_browser));
 }
 
-// http://crbug.com/470724
-// Kiosk mode on Mac has a location bar but it shouldn't.
 IN_PROC_BROWSER_TEST_F(PermissionBubbleKioskBrowserTest,
-                       DISABLED_KioskHasNoLocationBar) {
+                       KioskHasNoLocationBar) {
   ShowBubble(browser());
+  // Kiosk mode on Mac has no location bar.
   EXPECT_FALSE(HasVisibleLocationBarForBrowser(browser()));
 }
diff --git a/chrome/browser/ui/extensions/extension_message_bubble_factory.cc b/chrome/browser/ui/extensions/extension_message_bubble_factory.cc
index 6dbe4e9ea..d64dea3 100644
--- a/chrome/browser/ui/extensions/extension_message_bubble_factory.cc
+++ b/chrome/browser/ui/extensions/extension_message_bubble_factory.cc
@@ -6,8 +6,9 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
-#include "base/lazy_instance.h"
 #include "base/metrics/field_trial.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/dev_mode_bubble_delegate.h"
 #include "chrome/browser/extensions/extension_message_bubble_controller.h"
@@ -26,12 +27,6 @@
 
 namespace {
 
-// A map of all profiles evaluated, so we can tell if it's the initial check.
-// TODO(devlin): It would be nice to coalesce all the "profiles evaluated" maps
-// that are in the different bubble controllers.
-base::LazyInstance<std::set<Profile*>>::DestructorAtExit g_profiles_evaluated =
-    LAZY_INSTANCE_INITIALIZER;
-
 // This is used to turn on override whether bubbles are enabled or disabled for
 // testing.
 ExtensionMessageBubbleFactory::OverrideForTesting g_override_for_testing =
@@ -44,6 +39,14 @@
 const char kEnableProxyWarningExperimentName[] = "ExtensionProxyWarning";
 #endif
 
+// A set of all profiles evaluated, so we can tell if it's the initial check.
+// TODO(devlin): It would be nice to coalesce all the "profiles evaluated" maps
+// that are in the different bubble controllers.
+std::set<Profile*>& GetEvaluatedProfiles() {
+  static base::NoDestructor<std::set<Profile*>> s;
+  return *s;
+}
+
 bool IsExperimentEnabled(const char* experiment_name) {
   // Don't allow turning it off via command line.
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -115,12 +118,10 @@
 std::unique_ptr<extensions::ExtensionMessageBubbleController>
 ExtensionMessageBubbleFactory::GetController() {
   Profile* original_profile = browser_->profile()->GetOriginalProfile();
-  std::set<Profile*>& profiles_evaluated = g_profiles_evaluated.Get();
-  bool is_initial_check = profiles_evaluated.count(original_profile) == 0;
-  profiles_evaluated.insert(original_profile);
+  std::set<Profile*>& profiles_evaluated = GetEvaluatedProfiles();
+  bool is_initial_check = profiles_evaluated.insert(original_profile).second;
 
   std::unique_ptr<extensions::ExtensionMessageBubbleController> controller;
-
   if (g_override_for_testing == OVERRIDE_DISABLED)
     return controller;
 
diff --git a/chrome/browser/ui/forced_reauthentication_dialog.h b/chrome/browser/ui/forced_reauthentication_dialog.h
deleted file mode 100644
index 6beab174..0000000
--- a/chrome/browser/ui/forced_reauthentication_dialog.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_FORCED_REAUTHENTICATION_DIALOG_H_
-#define CHROME_BROWSER_UI_FORCED_REAUTHENTICATION_DIALOG_H_
-
-#include <memory>
-
-#include "base/macros.h"
-
-namespace identity {
-class IdentityManager;
-}
-
-class Profile;
-
-namespace base {
-class TimeDelta;
-}  // namespace base
-
-// The virtual class of ForcedReauthenticationDialog.
-class ForcedReauthenticationDialog {
- public:
-  static std::unique_ptr<ForcedReauthenticationDialog> Create();
-
-  virtual ~ForcedReauthenticationDialog() {}
-  // Show the ForcedReauthenticationDialog for |profile|. If there're no opened
-  // browser windows for |profile|, |identity_manager| will be called to signed
-  // out immediately. Otherwise, dialog will be closed with all browser windows
-  // are associated to |profile| after |countdown_duration| if there is no
-  // reauth.
-  virtual void ShowDialog(Profile* profile,
-                          identity::IdentityManager* identity_manager,
-                          base::TimeDelta countdown_duration) = 0;
-
- protected:
-  ForcedReauthenticationDialog() {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ForcedReauthenticationDialog);
-};
-
-#endif  // CHROME_BROWSER_UI_FORCED_REAUTHENTICATION_DIALOG_H_
diff --git a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
index 4a671d0c..03cd7df 100644
--- a/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
+++ b/chrome/browser/ui/permission_bubble/permission_bubble_browser_test_util.cc
@@ -15,7 +15,9 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
 
 TestPermissionBubbleViewDelegate::TestPermissionBubbleViewDelegate()
     : PermissionPrompt::Delegate() {
@@ -81,4 +83,8 @@
     base::CommandLine* command_line) {
   PermissionBubbleBrowserTest::SetUpCommandLine(command_line);
   command_line->AppendSwitch(switches::kKioskMode);
+  // Navigate to a test file URL.
+  GURL test_file_url(ui_test_utils::GetTestUrl(
+      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("simple.html"))));
+  command_line->AppendArg(test_file_url.spec());
 }
diff --git a/chrome/browser/ui/sync/profile_signin_confirmation_helper.h b/chrome/browser/ui/sync/profile_signin_confirmation_helper.h
index 30675726..65b6bc44 100644
--- a/chrome/browser/ui/sync/profile_signin_confirmation_helper.h
+++ b/chrome/browser/ui/sync/profile_signin_confirmation_helper.h
@@ -14,10 +14,6 @@
 
 class NativeTheme;
 
-// Blend parameters for the dialog prompt bar.
-const SkAlpha kSigninConfirmationPromptBarBackgroundAlpha = 0x0A;
-const SkAlpha kSigninConfirmationPromptBarBorderAlpha = 0x1F;
-
 // Create slightly different colors for the dialog prompt bar.
 SkColor GetSigninConfirmationPromptBarColor(NativeTheme* theme, SkAlpha alpha);
 
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index d0266c8d..4b6f16b 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -122,6 +122,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
+#include "chrome/browser/offline_pages/auto_fetch_page_load_watcher.h"
 #include "chrome/browser/offline_pages/offline_page_tab_helper.h"
 #include "chrome/browser/offline_pages/recent_tab_helper.h"
 #endif
@@ -323,6 +324,7 @@
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
 offline_pages::OfflinePageTabHelper::CreateForWebContents(web_contents);
 offline_pages::RecentTabHelper::CreateForWebContents(web_contents);
+offline_pages::AutoFetchPageLoadWatcher::CreateForWebContents(web_contents);
 #endif
 
 #if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
index 3ca52ec..17ce7ce 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.h"
 
+#include "base/command_line.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service.h"
@@ -19,6 +20,7 @@
 #include "chrome/browser/ui/views/frame/browser_view_layout.h"
 #include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/hit_test.h"
@@ -32,6 +34,9 @@
 
 FullscreenToolbarStyle GetUserPreferredToolbarStyle(
     const PrefService* pref_service) {
+  // In Kiosk mode, we don't show top Chrome UI.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
+    return FullscreenToolbarStyle::TOOLBAR_NONE;
   return pref_service->GetBoolean(prefs::kShowFullscreenToolbar)
              ? FullscreenToolbarStyle::TOOLBAR_PRESENT
              : FullscreenToolbarStyle::TOOLBAR_HIDDEN;
diff --git a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.cc b/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.cc
deleted file mode 100644
index 4178179..0000000
--- a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.cc
+++ /dev/null
@@ -1,288 +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/ui/views/profiles/forced_reauthentication_dialog_view.h"
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "base/i18n/message_formatter.h"
-#include "base/strings/string16.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/sync/profile_signin_confirmation_helper.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/views/chrome_layout_provider.h"
-#include "chrome/browser/ui/views/chrome_typography.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/grit/chromium_strings.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "components/signin/core/browser/signin_manager.h"
-#include "services/identity/public/cpp/identity_manager.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/views/background.h"
-#include "ui/views/border.h"
-#include "ui/views/controls/styled_label.h"
-#include "ui/views/layout/grid_layout.h"
-#include "ui/views/view.h"
-#include "ui/views/window/dialog_client_view.h"
-
-namespace {
-
-// Refresh title of the dialog every second.
-constexpr int kRefreshTitleTimer = 1;
-
-// If browser windows are going to be closed soon, close browser window before
-// showing sign in dialog because there might not be enough time for user to
-// finish sign in.
-constexpr int kCloseDirectlyTimer = 60;
-
-void Signout(identity::IdentityManager* identity_manager) {
-  identity_manager->ClearPrimaryAccount(
-      identity::IdentityManager::ClearAccountTokensAction::kDefault,
-      signin_metrics::AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN,
-      signin_metrics::SignoutDelete::KEEPING);
-}
-
-bool IsMatchingBrowser(Browser* browser, Profile* profile) {
-  return browser->profile()->GetOriginalProfile() ==
-             profile->GetOriginalProfile() &&
-         !browser->tab_strip_model()->empty() && browser->window()->IsVisible();
-}
-
-// Find a browser that is associated with |profile| to show the dialog for
-// Sign out warning.
-Browser* FindBrowserWithProfile(Profile* profile) {
-  Browser* browser = BrowserList::GetInstance()->GetLastActive();
-  if (browser && IsMatchingBrowser(browser, profile))
-    return browser;
-  for (auto* browser : *BrowserList::GetInstance()) {
-    if (IsMatchingBrowser(browser, profile)) {
-      return browser;
-    }
-  }
-  return nullptr;
-}
-
-// PromptLabel overrides the default insets of StyledLabel.
-class PromptLabel : public views::StyledLabel {
- public:
-  PromptLabel(const base::string16& text, views::StyledLabelListener* listener)
-      : views::StyledLabel(text, listener) {}
-
-  gfx::Insets GetInsets() const override {
-    return ChromeLayoutProvider::Get()->GetInsetsMetric(
-        views::INSETS_DIALOG_SUBSECTION);
-  }
-};
-
-}  // namespace
-
-// ForcedReauthenticationDialogView
-
-ForcedReauthenticationDialogView::ForcedReauthenticationDialogView(
-    Browser* browser,
-    identity::IdentityManager* identity_manager,
-    base::TimeDelta countdown_duration)
-    : browser_(browser),
-      identity_manager_(identity_manager),
-      desired_close_time_(base::TimeTicks::Now() + countdown_duration),
-      weak_factory_(this) {
-  constrained_window::CreateBrowserModalDialogViews(
-      this, browser->window()->GetNativeWindow())
-      ->Show();
-  browser->window()->FlashFrame(true);
-  browser->window()->Activate();
-}
-
-ForcedReauthenticationDialogView::~ForcedReauthenticationDialogView() {}
-
-// static
-ForcedReauthenticationDialogView* ForcedReauthenticationDialogView::ShowDialog(
-    Profile* profile,
-    identity::IdentityManager* identity_manager,
-    base::TimeDelta countdown_duration) {
-  Browser* browser = FindBrowserWithProfile(profile);
-  if (browser == nullptr) {  // If there is no browser, we can just sign
-                             // out profile directly.
-    Signout(identity_manager);
-    return nullptr;
-  }
-
-  return new ForcedReauthenticationDialogView(browser, identity_manager,
-                                              countdown_duration);
-}
-
-bool ForcedReauthenticationDialogView::Accept() {
-  if (GetTimeRemaining() < base::TimeDelta::FromSeconds(kCloseDirectlyTimer)) {
-    Signout(identity_manager_);
-  } else {
-    browser_->signin_view_controller()->ShowSignin(
-        profiles::BubbleViewMode::BUBBLE_VIEW_MODE_GAIA_REAUTH, browser_,
-        signin_metrics::AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING);
-  }
-  return true;
-}
-
-bool ForcedReauthenticationDialogView::Cancel() {
-  return true;
-}
-
-void ForcedReauthenticationDialogView::WindowClosing() {
-  refresh_timer_.Stop();
-}
-
-base::string16 ForcedReauthenticationDialogView::GetWindowTitle() const {
-  base::TimeDelta time_left = GetTimeRemaining();
-  return base::i18n::MessageFormatter::FormatWithNumberedArgs(
-      l10n_util::GetStringUTF16(IDS_ENTERPRISE_FORCE_SIGNOUT_TITLE),
-      time_left.InMinutes(), time_left.InSeconds() % 60);
-}
-
-base::string16 ForcedReauthenticationDialogView::GetDialogButtonLabel(
-    ui::DialogButton button) const {
-  if (button == ui::DIALOG_BUTTON_OK) {
-    return l10n_util::GetStringUTF16(
-        IDS_ENTERPRISE_FORCE_SIGNOUT_CLOSE_CONFIRM);
-  }
-  return l10n_util::GetStringUTF16(IDS_ENTERPRISE_FORCE_SIGNOUT_CLOSE_DELAY);
-}
-
-ui::ModalType ForcedReauthenticationDialogView::GetModalType() const {
-  return ui::MODAL_TYPE_WINDOW;
-}
-
-void ForcedReauthenticationDialogView::AddedToWidget() {
-  const SkColor prompt_bar_background_color =
-      GetSigninConfirmationPromptBarColor(
-          GetNativeTheme(), ui::kSigninConfirmationPromptBarBackgroundAlpha);
-  // Create the prompt label.
-  size_t offset;
-  std::string email = identity_manager_->GetPrimaryAccountInfo().email;
-  const base::string16 domain =
-      base::ASCIIToUTF16(gaia::ExtractDomainName(email));
-  const base::string16 prompt_text =
-      l10n_util::GetStringFUTF16(IDS_ENTERPRISE_SIGNIN_ALERT, domain, &offset);
-
-  // Create the prompt label.
-  PromptLabel* prompt_label = new PromptLabel(prompt_text, nullptr);
-  prompt_label->SetDisplayedOnBackgroundColor(prompt_bar_background_color);
-
-  views::StyledLabel::RangeStyleInfo bold_style;
-  bold_style.text_style = STYLE_EMPHASIZED;
-  prompt_label->AddStyleRange(gfx::Range(offset, offset + domain.size()),
-                              bold_style);
-
-  prompt_label->SetBorder(views::CreateSolidSidedBorder(
-      1, 0, 1, 0,
-      ui::GetSigninConfirmationPromptBarColor(
-          GetNativeTheme(), ui::kSigninConfirmationPromptBarBorderAlpha)));
-  prompt_label->SetBackground(
-      views::CreateSolidBackground(prompt_bar_background_color));
-
-  // Create the explanation label.
-  base::string16 signin_explanation_text;
-  base::string16 close_warning;
-  close_warning = l10n_util::GetStringUTF16(
-      IDS_ENTERPRISE_FORCE_SIGNOUT_ADDITIONAL_EXPLANATION);
-  if (email.empty()) {
-    signin_explanation_text = l10n_util::GetStringFUTF16(
-        IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION_WITHOUT_USER_NAME,
-        close_warning);
-  } else {
-    signin_explanation_text =
-        l10n_util::GetStringFUTF16(IDS_ENTERPRISE_FORCE_SIGNOUT_EXPLANATION,
-                                   base::ASCIIToUTF16(email), close_warning);
-  }
-  views::StyledLabel* explanation_label =
-      new views::StyledLabel(signin_explanation_text, nullptr);
-
-  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
-  // Layout the components.
-  const gfx::Insets dialog_insets =
-      provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT);
-  SetBorder(views::CreateEmptyBorder(dialog_insets.top(), 0,
-                                     dialog_insets.bottom(), 0));
-  views::GridLayout* dialog_layout =
-      SetLayoutManager(std::make_unique<views::GridLayout>(this));
-
-  // Use a column set with no padding.
-  dialog_layout->AddColumnSet(0)->AddColumn(views::GridLayout::FILL,
-                                            views::GridLayout::FILL, 1.0,
-                                            views::GridLayout::USE_PREF, 0, 0);
-  dialog_layout->StartRow(views::GridLayout::kFixedSize, 0);
-  dialog_layout->AddView(prompt_label, 1, 1, views::GridLayout::FILL,
-                         views::GridLayout::FILL, 0, 0);
-
-  // Use a new column set for the explanation label so we can add padding.
-  dialog_layout->AddPaddingRow(views::GridLayout::kFixedSize,
-                               dialog_insets.top());
-  views::ColumnSet* explanation_columns = dialog_layout->AddColumnSet(1);
-
-  explanation_columns->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                        dialog_insets.left());
-  explanation_columns->AddColumn(views::GridLayout::FILL,
-                                 views::GridLayout::FILL, 1.0,
-                                 views::GridLayout::USE_PREF, 0, 0);
-  explanation_columns->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                        dialog_insets.right());
-  dialog_layout->StartRow(views::GridLayout::kFixedSize, 1);
-  const int kPreferredWidth = 440;
-  dialog_layout->AddView(explanation_label, 1, 1, views::GridLayout::FILL,
-                         views::GridLayout::FILL, kPreferredWidth,
-                         explanation_label->GetHeightForWidth(kPreferredWidth));
-  refresh_timer_.Start(FROM_HERE,
-                       base::TimeDelta::FromSeconds(kRefreshTitleTimer), this,
-                       &ForcedReauthenticationDialogView::OnCountDown);
-}
-
-void ForcedReauthenticationDialogView::CloseDialog() {
-  GetWidget()->Close();
-}
-
-void ForcedReauthenticationDialogView::OnCountDown() {
-  if (desired_close_time_ <= base::TimeTicks::Now()) {
-    Cancel();
-    GetWidget()->Close();
-  }
-  GetWidget()->UpdateWindowTitle();
-}
-
-base::TimeDelta ForcedReauthenticationDialogView::GetTimeRemaining() const {
-  base::TimeTicks now = base::TimeTicks::Now();
-  if (desired_close_time_ <= now)
-    return base::TimeDelta();
-  return desired_close_time_ - now;
-}
-
-// ForcedReauthenticationDialogImpl
-
-ForcedReauthenticationDialogImpl::ForcedReauthenticationDialogImpl() {}
-ForcedReauthenticationDialogImpl::~ForcedReauthenticationDialogImpl() {
-  if (dialog_view_)
-    dialog_view_->CloseDialog();
-}
-
-void ForcedReauthenticationDialogImpl::ShowDialog(
-    Profile* profile,
-    identity::IdentityManager* identity_manager,
-    base::TimeDelta countdown_duration) {
-  dialog_view_ = ForcedReauthenticationDialogView::ShowDialog(
-                     profile, identity_manager, countdown_duration)
-                     ->AsWeakPtr();
-}
-
-// ForcedReauthenticationDialog
-
-// static
-std::unique_ptr<ForcedReauthenticationDialog>
-ForcedReauthenticationDialog::Create() {
-  return std::make_unique<ForcedReauthenticationDialogImpl>();
-}
diff --git a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.h b/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.h
deleted file mode 100644
index d4a9885..0000000
--- a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view.h
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_PROFILES_FORCED_REAUTHENTICATION_DIALOG_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_PROFILES_FORCED_REAUTHENTICATION_DIALOG_VIEW_H_
-
-#include <memory>
-#include <string>
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/timer/timer.h"
-#include "chrome/browser/ui/forced_reauthentication_dialog.h"
-#include "ui/views/controls/button/button.h"
-#include "ui/views/window/dialog_delegate.h"
-
-namespace identity {
-class IdentityManager;
-}
-
-class Browser;
-class Profile;
-
-// A modal dialog that displays a warning message of the auth failure
-// and ask user to sign in again.
-class ForcedReauthenticationDialogView : public views::DialogDelegateView {
- public:
-  ~ForcedReauthenticationDialogView() override;
-
-  // Shows a warning dialog for |profile|. If there are no Browser windows
-  // associated with |profile|, signs out the profile immediately, otherwise the
-  // user can clicks accept to sign in again. Dialog will be closed after
-  // |countdown_duration| seconds.
-  // Dialog will delete itself after closing.
-  static ForcedReauthenticationDialogView* ShowDialog(
-      Profile* profile,
-      identity::IdentityManager* identity_manager,
-      base::TimeDelta countdown_duration);
-
-  // override views::DialogDelegateView
-  bool Accept() override;
-  bool Cancel() override;
-  void WindowClosing() override;
-  base::string16 GetWindowTitle() const override;
-  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
-  ui::ModalType GetModalType() const override;
-
-  // override views::View
-  void AddedToWidget() override;
-
-  // Close the dialog
-  void CloseDialog();
-
-  base::WeakPtr<ForcedReauthenticationDialogView> AsWeakPtr() {
-    return weak_factory_.GetWeakPtr();
-  }
-
- private:
-  // Show the dialog for |browser|. The dialog will delete itself after closing.
-  ForcedReauthenticationDialogView(Browser* browser,
-                                   identity::IdentityManager* identity_manager,
-                                   base::TimeDelta countdown_duration);
-
-  void OnCountDown();
-  base::TimeDelta GetTimeRemaining() const;
-
-  Browser* const browser_;
-  identity::IdentityManager* identity_manager_;
-
-  const base::TimeTicks desired_close_time_;
-
-  // The timer which is used to refresh the dialog title to display the
-  // remaining time.
-  base::RepeatingTimer refresh_timer_;
-
-  base::WeakPtrFactory<ForcedReauthenticationDialogView> weak_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ForcedReauthenticationDialogView);
-};
-
-class ForcedReauthenticationDialogImpl : public ForcedReauthenticationDialog {
- public:
-  ForcedReauthenticationDialogImpl();
-  ~ForcedReauthenticationDialogImpl() override;
-
-  // override ForcedReauthenticationDialog
-  void ShowDialog(Profile* profile,
-                  identity::IdentityManager* identity_manager,
-                  base::TimeDelta countdown_duration) override;
-
- private:
-  base::WeakPtr<ForcedReauthenticationDialogView> dialog_view_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_PROFILES_FORCED_REAUTHENTICATION_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view_browsertest.cc b/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view_browsertest.cc
deleted file mode 100644
index 8080fdb..0000000
--- a/chrome/browser/ui/views/profiles/forced_reauthentication_dialog_view_browsertest.cc
+++ /dev/null
@@ -1,72 +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/ui/views/profiles/forced_reauthentication_dialog_view.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
-#include "chrome/browser/signin/signin_manager_factory.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/test/test_browser_dialog.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "services/identity/public/cpp/identity_manager.h"
-#include "services/identity/public/cpp/identity_test_utils.h"
-#include "ui/base/ui_base_types.h"
-
-class ForcedReauthenticationDialogViewBrowserTest : public DialogBrowserTest {
- public:
-  ForcedReauthenticationDialogViewBrowserTest() {}
-
-  // override DialogBrowserTest
-  void ShowUi(const std::string& name) override {
-    Profile* profile = browser()->profile();
-    identity::IdentityManager* manager =
-        IdentityManagerFactory::GetForProfile(profile);
-
-    identity::MakePrimaryAccountAvailable(
-        SigninManagerFactory::GetForProfile(profile),
-        ProfileOAuth2TokenServiceFactory::GetForProfile(profile), manager,
-        "test@xyz.com");
-
-    ForcedReauthenticationDialogView::ShowDialog(
-        profile, manager, base::TimeDelta::FromSeconds(60));
-  }
-
-  // An integer represents the buttons of dialog.
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ForcedReauthenticationDialogViewBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_F(ForcedReauthenticationDialogViewBrowserTest,
-                       InvokeUi_default) {
-  ShowAndVerifyUi();
-}
-
-// Dialog will not be display if there is no valid browser window.
-IN_PROC_BROWSER_TEST_F(ForcedReauthenticationDialogViewBrowserTest,
-                       NotOpenDialogDueToNoBrowser) {
-  Profile* profile = browser()->profile();
-  CloseBrowserSynchronously(browser());
-  EXPECT_EQ(nullptr,
-            ForcedReauthenticationDialogView::ShowDialog(
-                profile, IdentityManagerFactory::GetForProfile(profile),
-                base::TimeDelta::FromSeconds(60)));
-}
-
-IN_PROC_BROWSER_TEST_F(ForcedReauthenticationDialogViewBrowserTest,
-                       NotOpenDialogDueToNoTabs) {
-  Profile* profile = browser()->profile();
-  TabStripModel* model = browser()->tab_strip_model();
-  ASSERT_EQ(1, model->count());
-  model->CloseWebContentsAt(0, TabStripModel::CLOSE_NONE);
-  ASSERT_TRUE(model->empty());
-  EXPECT_EQ(nullptr,
-            ForcedReauthenticationDialogView::ShowDialog(
-                profile, IdentityManagerFactory::GetForProfile(profile),
-                base::TimeDelta::FromSeconds(60)));
-}
diff --git a/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc b/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
index eef4d37..6ff28f4 100644
--- a/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
+++ b/chrome/browser/ui/views/sync/profile_signin_confirmation_dialog_views.cc
@@ -152,8 +152,7 @@
     return;
 
   const SkColor kPromptBarBackgroundColor =
-      ui::GetSigninConfirmationPromptBarColor(
-          GetNativeTheme(), ui::kSigninConfirmationPromptBarBackgroundAlpha);
+      ui::GetSigninConfirmationPromptBarColor(GetNativeTheme(), 0x0A);
 
   // Create business icon.
   int business_icon_size = 20;
@@ -183,8 +182,7 @@
   views::View* prompt_bar = new views::View;
   prompt_bar->SetBorder(views::CreateSolidSidedBorder(
       1, 0, 1, 0,
-      ui::GetSigninConfirmationPromptBarColor(
-          GetNativeTheme(), ui::kSigninConfirmationPromptBarBorderAlpha)));
+      ui::GetSigninConfirmationPromptBarColor(GetNativeTheme(), 0x1F)));
   prompt_bar->SetBackground(
       views::CreateSolidBackground(kPromptBarBackgroundColor));
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 4a95bae..c6cc7cfc 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -429,8 +429,14 @@
 void TabStrip::AddTabAt(int model_index, TabRendererData data, bool is_active) {
   const bool was_single_tab_mode = SingleTabMode();
 
+  // Get view child index of where we want to insert
+  int view_index = 0;
+  if (model_index > 0) {
+    view_index = GetIndexOf(tab_at(model_index - 1)) + 1;
+  }
+
   Tab* tab = new Tab(this, animation_container_.get());
-  AddChildView(tab);
+  AddChildViewAt(tab, view_index);
   const bool pinned = data.pinned;
   tab->SetData(std::move(data));
   UpdateTabsClosingMap(model_index, 1);
@@ -487,8 +493,14 @@
                        int to_model_index,
                        TabRendererData data) {
   DCHECK_GT(tabs_.view_size(), 0);
+
   const Tab* last_tab = GetLastVisibleTab();
   tab_at(from_model_index)->SetData(std::move(data));
+
+  // Keep child views in same order as tab strip model.
+  const int to_view_index = GetIndexOf(tab_at(to_model_index));
+  ReorderChildView(tab_at(from_model_index), to_view_index);
+
   if (touch_layout_) {
     tabs_.MoveViewOnly(from_model_index, to_model_index);
     int pinned_count = 0;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index 3ef3a6e..5e351b1 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -311,6 +311,40 @@
   EXPECT_EQ(0, observer.last_tab_removed());
 }
 
+namespace {
+
+bool TabViewsInOrder(TabStrip* tab_strip) {
+  for (int i = 1; i < tab_strip->tab_count(); ++i) {
+    Tab* left = tab_strip->tab_at(i - 1);
+    Tab* right = tab_strip->tab_at(i);
+
+    if (tab_strip->GetIndexOf(right) < tab_strip->GetIndexOf(left)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+// Verifies child view order matches model order.
+TEST_P(TabStripTest, TabViewOrder) {
+  controller_->AddTab(0, false);
+  controller_->AddTab(1, false);
+  controller_->AddTab(2, false);
+  EXPECT_TRUE(TabViewsInOrder(tab_strip_));
+
+  tab_strip_->MoveTab(0, 1, TabRendererData());
+  EXPECT_TRUE(TabViewsInOrder(tab_strip_));
+  tab_strip_->MoveTab(1, 2, TabRendererData());
+  EXPECT_TRUE(TabViewsInOrder(tab_strip_));
+  tab_strip_->MoveTab(1, 0, TabRendererData());
+  EXPECT_TRUE(TabViewsInOrder(tab_strip_));
+  tab_strip_->MoveTab(0, 2, TabRendererData());
+  EXPECT_TRUE(TabViewsInOrder(tab_strip_));
+}
+
 TEST_P(TabStripTest, VisibilityInOverflow) {
   constexpr int kInitialWidth = 250;
   tab_strip_->SetBounds(0, 0, kInitialWidth, 20);
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 96b2db8..58c08c1 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -235,6 +235,8 @@
     "location_bar_helper.cc",
     "location_bar_helper.h",
     "platform_controller.h",
+    "platform_controller_for_testing.cc",
+    "platform_controller_for_testing.h",
     "sample_queue.cc",
     "sample_queue.h",
     "scheduler_browser_renderer_interface.h",
diff --git a/chrome/browser/vr/input_delegate_for_testing.cc b/chrome/browser/vr/input_delegate_for_testing.cc
index 1cd0d55..d244b1f 100644
--- a/chrome/browser/vr/input_delegate_for_testing.cc
+++ b/chrome/browser/vr/input_delegate_for_testing.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/vr/input_delegate_for_testing.h"
 
 #include "chrome/browser/vr/input_event.h"
+#include "chrome/browser/vr/platform_controller_for_testing.h"
 #include "chrome/browser/vr/ui_interface.h"
 #include "chrome/browser/vr/ui_scene_constants.h"
 #include "chrome/browser/vr/ui_test_input.h"
@@ -35,6 +36,7 @@
 namespace vr {
 
 InputDelegateForTesting::InputDelegateForTesting(UiInterface* ui) : ui_(ui) {
+  gesture_detector_ = std::make_unique<GestureDetector>();
   cached_controller_model_.laser_direction = kForwardVector;
   SetOriginAndTransform(&cached_controller_model_);
 }
@@ -54,23 +56,17 @@
   DCHECK_NE(controller_input.action,
             VrControllerTestAction::kRevertToRealInput);
   ControllerModel controller_model;
-  auto target_point = ui_->GetTargetPointForTesting(
-      controller_input.element_name, controller_input.position);
-  auto direction = (target_point - kStartControllerPosition) - kOrigin;
-  direction.GetNormalized(&controller_model.laser_direction);
+  if (controller_input.element_name == UserFriendlyElementName::kNone) {
+    controller_model.laser_direction = kForwardVector;
+  } else {
+    auto target_point = ui_->GetTargetPointForTesting(
+        controller_input.element_name, controller_input.position);
+    auto direction = (target_point - kStartControllerPosition) - kOrigin;
+    direction.GetNormalized(&controller_model.laser_direction);
+  }
   SetOriginAndTransform(&controller_model);
 
   switch (controller_input.action) {
-    case VrControllerTestAction::kClick:
-      // Add in the button down action.
-      controller_model.touchpad_button_state =
-          ControllerModel::ButtonState::kDown;
-      controller_model_queue_.push(controller_model);
-      // Add in the button up action.
-      controller_model.touchpad_button_state =
-          ControllerModel::ButtonState::kUp;
-      controller_model_queue_.push(controller_model);
-      break;
     case VrControllerTestAction::kHover:
       FALLTHROUGH;
     case VrControllerTestAction::kClickUp:
@@ -94,6 +90,14 @@
       }
       controller_model_queue_.push(controller_model);
       break;
+    case VrControllerTestAction::kAppDown:
+      controller_model.app_button_state = ControllerModel::ButtonState::kDown;
+      controller_model_queue_.push(controller_model);
+      break;
+    case VrControllerTestAction::kAppUp:
+      controller_model.app_button_state = ControllerModel::ButtonState::kUp;
+      controller_model_queue_.push(controller_model);
+      break;
     default:
       NOTREACHED() << "Given unsupported controller action";
   }
@@ -106,6 +110,7 @@
 void InputDelegateForTesting::UpdateController(const gfx::Transform& head_pose,
                                                base::TimeTicks current_time,
                                                bool is_webxr_frame) {
+  previous_controller_model_ = cached_controller_model_;
   if (!controller_model_queue_.empty()) {
     cached_controller_model_ = controller_model_queue_.front();
     controller_model_queue_.pop();
@@ -121,7 +126,9 @@
 
 InputEventList InputDelegateForTesting::GetGestures(
     base::TimeTicks current_time) {
-  return InputEventList();
+  PlatformControllerForTesting controller(&previous_controller_model_,
+                                          &cached_controller_model_);
+  return gesture_detector_->DetectGestures(controller, current_time);
 }
 
 device::mojom::XRInputSourceStatePtr
diff --git a/chrome/browser/vr/input_delegate_for_testing.h b/chrome/browser/vr/input_delegate_for_testing.h
index af494d6..ce56d827 100644
--- a/chrome/browser/vr/input_delegate_for_testing.h
+++ b/chrome/browser/vr/input_delegate_for_testing.h
@@ -8,6 +8,7 @@
 #include <queue>
 
 #include "base/macros.h"
+#include "chrome/browser/vr/gesture_detector.h"
 #include "chrome/browser/vr/input_delegate.h"
 #include "chrome/browser/vr/model/controller_model.h"
 
@@ -40,6 +41,8 @@
   UiInterface* ui_;
   std::queue<ControllerModel> controller_model_queue_;
   ControllerModel cached_controller_model_;
+  ControllerModel previous_controller_model_;
+  std::unique_ptr<GestureDetector> gesture_detector_;
 
   DISALLOW_COPY_AND_ASSIGN(InputDelegateForTesting);
 };
diff --git a/chrome/browser/vr/platform_controller_for_testing.cc b/chrome/browser/vr/platform_controller_for_testing.cc
new file mode 100644
index 0000000..7f80141
--- /dev/null
+++ b/chrome/browser/vr/platform_controller_for_testing.cc
@@ -0,0 +1,99 @@
+// 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/vr/platform_controller_for_testing.h"
+#include "base/time/time.h"
+#include "chrome/browser/vr/model/controller_model.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace vr {
+
+PlatformControllerForTesting::PlatformControllerForTesting(
+    ControllerModel* prev_model,
+    ControllerModel* cur_model)
+    : prev_model_(prev_model), cur_model_(cur_model) {}
+
+bool PlatformControllerForTesting::IsButtonDown(
+    PlatformController::ButtonType type) const {
+  switch (type) {
+    case PlatformController::ButtonType::kButtonMenu:
+      return cur_model_->app_button_state ==
+             ControllerModel::ButtonState::kDown;
+    case PlatformController::ButtonType::kButtonSelect:
+      return cur_model_->touchpad_button_state ==
+             ControllerModel::ButtonState::kDown;
+    default:
+      return false;
+  }
+}
+
+bool PlatformControllerForTesting::ButtonUpHappened(
+    PlatformController::ButtonType type) const {
+  switch (type) {
+    case PlatformController::ButtonType::kButtonMenu:
+      return (cur_model_->app_button_state ==
+                  ControllerModel::ButtonState::kUp &&
+              cur_model_->app_button_state != prev_model_->app_button_state);
+    case PlatformController::ButtonType::kButtonSelect:
+      return (cur_model_->touchpad_button_state ==
+                  ControllerModel::ButtonState::kUp &&
+              cur_model_->touchpad_button_state !=
+                  prev_model_->touchpad_button_state);
+    default:
+      return false;
+  }
+}
+
+bool PlatformControllerForTesting::ButtonDownHappened(
+    PlatformController::ButtonType type) const {
+  switch (type) {
+    case PlatformController::ButtonType::kButtonMenu:
+      return (cur_model_->app_button_state ==
+                  ControllerModel::ButtonState::kDown &&
+              cur_model_->app_button_state != prev_model_->app_button_state);
+    case PlatformController::ButtonType::kButtonSelect:
+      return (cur_model_->touchpad_button_state ==
+                  ControllerModel::ButtonState::kDown &&
+              cur_model_->touchpad_button_state !=
+                  prev_model_->touchpad_button_state);
+    default:
+      return false;
+  }
+}
+
+bool PlatformControllerForTesting::IsTouchingTrackpad() const {
+  return false;
+}
+
+gfx::PointF PlatformControllerForTesting::GetPositionInTrackpad() const {
+  return gfx::PointF();
+}
+
+base::TimeTicks PlatformControllerForTesting::GetLastOrientationTimestamp()
+    const {
+  return prev_model_->last_orientation_timestamp;
+}
+
+base::TimeTicks PlatformControllerForTesting::GetLastTouchTimestamp() const {
+  return prev_model_->last_button_timestamp;
+}
+
+base::TimeTicks PlatformControllerForTesting::GetLastButtonTimestamp() const {
+  return prev_model_->last_button_timestamp;
+}
+
+ControllerModel::Handedness PlatformControllerForTesting::GetHandedness()
+    const {
+  return ControllerModel::Handedness::kRightHanded;
+}
+
+bool PlatformControllerForTesting::GetRecentered() const {
+  return false;
+}
+
+int PlatformControllerForTesting::GetBatteryLevel() const {
+  return 100;
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/platform_controller_for_testing.h b/chrome/browser/vr/platform_controller_for_testing.h
new file mode 100644
index 0000000..0964c94c
--- /dev/null
+++ b/chrome/browser/vr/platform_controller_for_testing.h
@@ -0,0 +1,41 @@
+// 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_VR_PLATFORM_CONTROLLER_FOR_TESTING_H_
+#define CHROME_BROWSER_VR_PLATFORM_CONTROLLER_FOR_TESTING_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "chrome/browser/vr/model/controller_model.h"
+#include "chrome/browser/vr/platform_controller.h"
+
+namespace vr {
+
+class PlatformControllerForTesting : public PlatformController {
+ public:
+  explicit PlatformControllerForTesting(ControllerModel* prev_model,
+                                        ControllerModel* cur_model);
+  ~PlatformControllerForTesting() override{};
+
+  bool IsButtonDown(PlatformController::ButtonType type) const override;
+  bool ButtonUpHappened(PlatformController::ButtonType type) const override;
+  bool ButtonDownHappened(PlatformController::ButtonType type) const override;
+  bool IsTouchingTrackpad() const override;
+  gfx::PointF GetPositionInTrackpad() const override;
+  base::TimeTicks GetLastOrientationTimestamp() const override;
+  base::TimeTicks GetLastTouchTimestamp() const override;
+  base::TimeTicks GetLastButtonTimestamp() const override;
+  ControllerModel::Handedness GetHandedness() const override;
+  bool GetRecentered() const override;
+  int GetBatteryLevel() const override;
+
+ private:
+  ControllerModel* prev_model_;
+  ControllerModel* cur_model_;
+  DISALLOW_COPY_AND_ASSIGN(PlatformControllerForTesting);
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_VR_PLATFORM_CONTROLLER_FOR_TESTING_H_
diff --git a/chrome/browser/vr/ui_test_input.h b/chrome/browser/vr/ui_test_input.h
index cd0c03c..74bab8c 100644
--- a/chrome/browser/vr/ui_test_input.h
+++ b/chrome/browser/vr/ui_test_input.h
@@ -14,7 +14,9 @@
 // element names for interaction during testing.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.vr
 enum class UserFriendlyElementName : int {
-  kUrl = 0,          // URL bar
+  kNone = 0,         // A special "element" that causes the controller to point
+                     // straight forward.
+  kUrl,              // URL bar
   kBackButton,       // Back button on the URL bar
   kForwardButton,    // Forward button in the overflow menu
   kReloadButton,     // Reload button in the overflow menu
@@ -68,13 +70,14 @@
 // element using simulated controller input during testing.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.vr
 enum class VrControllerTestAction : int {
-  kClick,
   kHover,
   kEnableMockedInput,
   kRevertToRealInput,
   kClickDown,
   kClickUp,
   kMove,
+  kAppDown,
+  kAppUp,
 };
 
 // These are used to specify what type of keyboard input should be performed
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 30b65e3..0c782a1 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -88,6 +88,7 @@
     TransportAvailabilityInfo transport_availability,
     base::Optional<device::FidoTransportProtocol> last_used_transport) {
   DCHECK_EQ(current_step(), Step::kNotStarted);
+  DCHECK(!transport_availability.disable_embedder_ui);
 
   transport_availability_ = std::move(transport_availability);
   last_used_transport_ = last_used_transport;
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index a2ec68b..bd66107 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -62,10 +62,6 @@
 #endif
 }
 
-bool IsWebAuthnUiEnabled() {
-  return base::FeatureList::IsEnabled(features::kWebAuthenticationUI);
-}
-
 }  // namespace
 
 #if defined(OS_MACOSX)
@@ -267,6 +263,11 @@
 void ChromeAuthenticatorRequestDelegate::OnTransportAvailabilityEnumerated(
     device::FidoRequestHandlerBase::TransportAvailabilityInfo data) {
 #if !defined(OS_ANDROID)
+  if (data.disable_embedder_ui) {
+    disable_ui_ = true;
+    return;
+  }
+
   if (!IsWebAuthnUiEnabled())
     return;
 
@@ -282,11 +283,14 @@
 
 bool ChromeAuthenticatorRequestDelegate::EmbedderControlsAuthenticatorDispatch(
     const device::FidoAuthenticator& authenticator) {
-  // TODO(hongjunchoi): Change this so that requests for BLE authenticators are
-  // not dispatched immediately if WebAuthN UI is enabled.
+  // TODO(hongjunchoi): Change this so that requests for BLE authenticators
+  // are not dispatched immediately if WebAuthN UI is enabled.
   if (!IsWebAuthnUiEnabled())
     return false;
 
+  // On macOS, a native dialog is shown for the Touch ID authenticator
+  // immediately after dispatch to that authenticator. This dialog must not
+  // be triggered before Chrome's WebAuthn UI has advanced accordingly.
   return authenticator.AuthenticatorTransport() &&
          *authenticator.AuthenticatorTransport() ==
              device::FidoTransportProtocol::kInternal;
@@ -384,3 +388,10 @@
       Profile::FromBrowserContext(browser_context())->GetPrefs();
   return prefs->GetList(kWebAuthnBlePairedMacAddressesPrefName);
 }
+
+bool ChromeAuthenticatorRequestDelegate::IsWebAuthnUiEnabled() const {
+  // UI can be disabled via flag or by the request handler for certain
+  // requests (e.g. on Windows, where the native API renders its own UI).
+  return base::FeatureList::IsEnabled(features::kWebAuthenticationUI) &&
+         !disable_ui_;
+}
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
index 6fd5c9d..680c8be 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
@@ -106,6 +106,7 @@
   void AddFidoBleDeviceToPairedList(std::string device_address);
   base::Optional<device::FidoTransportProtocol> GetLastTransportUsed() const;
   const base::ListValue* GetPreviouslyPairedFidoBleDeviceAddresses() const;
+  bool IsWebAuthnUiEnabled() const;
 
   content::RenderFrameHost* const render_frame_host_;
   AuthenticatorRequestDialogModel* weak_dialog_model_ = nullptr;
@@ -119,6 +120,11 @@
   base::OnceClosure cancel_callback_;
   device::FidoRequestHandlerBase::RequestCallback request_callback_;
 
+  // If in the TransportAvailabilityInfo reported by the request handler,
+  // disable_embedder_ui is set, this will be set to true. No UI must be
+  // rendered and all request handler callbacks will be ignored.
+  bool disable_ui_ = false;
+
   base::WeakPtrFactory<ChromeAuthenticatorRequestDelegate> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ChromeAuthenticatorRequestDelegate);
diff --git a/chrome/chrome_cleaner/BUILD.gn b/chrome/chrome_cleaner/BUILD.gn
index 389300fb9..cffb815 100644
--- a/chrome/chrome_cleaner/BUILD.gn
+++ b/chrome/chrome_cleaner/BUILD.gn
@@ -37,9 +37,9 @@
     "//chrome/chrome_cleaner/http:unittest_sources",
     "//chrome/chrome_cleaner/interfaces/typemaps:unittest_sources",
     "//chrome/chrome_cleaner/ipc:unittest_sources",
-    "//chrome/chrome_cleaner/json_parser:unittest_sources",
     "//chrome/chrome_cleaner/logging:unittest_sources",
     "//chrome/chrome_cleaner/os:unittest_sources",
+    "//chrome/chrome_cleaner/parsers/json_parser:unittest_sources",
     "//chrome/chrome_cleaner/pup_data:unittest_sources",
     "//chrome/chrome_cleaner/scanner:unittest_sources",
     "//chrome/chrome_cleaner/settings:unittest_sources",
diff --git a/chrome/chrome_cleaner/DEPS b/chrome/chrome_cleaner/DEPS
index 8f02ad1f..f7285e7 100644
--- a/chrome/chrome_cleaner/DEPS
+++ b/chrome/chrome_cleaner/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
-  "+sandbox/win/src",
-  "+testing/gtest",
   "+components/chrome_cleaner",
+  "+sandbox/win/src",
   "+third_party/protobuf/src/google/protobuf",
 ]
diff --git a/chrome/chrome_cleaner/chrome_utils/BUILD.gn b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
index d63af1a..d9fc74c 100644
--- a/chrome/chrome_cleaner/chrome_utils/BUILD.gn
+++ b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
@@ -24,8 +24,8 @@
   deps = [
     ":chrome_util_lib",
     "//base:base",
-    "//chrome/chrome_cleaner/json_parser",
     "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/json_parser",
   ]
 }
 
@@ -40,7 +40,7 @@
     ":extensions_util_lib",
     "//base:base",
     "//base/test:test_support",
-    "//chrome/chrome_cleaner/json_parser",
+    "//chrome/chrome_cleaner/parsers/json_parser",
     "//chrome/chrome_cleaner/test:test_util",
     "//testing/gtest",
   ]
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util.h b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
index 1564fe7..37a3efc 100644
--- a/chrome/chrome_cleaner/chrome_utils/extensions_util.h
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
@@ -11,8 +11,8 @@
 #include "base/macros.h"
 #include "base/strings/string16.h"
 #include "base/synchronization/waitable_event.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_api.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 
 namespace chrome_cleaner {
 
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
index 8d275e7..1160219 100644
--- a/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
@@ -17,7 +17,7 @@
 #include "base/test/test_reg_util_win.h"
 #include "base/test/test_timeouts.h"
 #include "base/win/registry.h"
-#include "chrome/chrome_cleaner/json_parser/test_json_parser.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h"
 #include "chrome/chrome_cleaner/test/test_file_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/chrome_cleaner/components/BUILD.gn b/chrome/chrome_cleaner/components/BUILD.gn
index 1b81bd1..422e53f9 100644
--- a/chrome/chrome_cleaner/components/BUILD.gn
+++ b/chrome/chrome_cleaner/components/BUILD.gn
@@ -25,12 +25,12 @@
     "//chrome/chrome_cleaner/constants:uws_id",
     "//chrome/chrome_cleaner/http:http",
     "//chrome/chrome_cleaner/http:http_status_codes",
-    "//chrome/chrome_cleaner/json_parser",
     "//chrome/chrome_cleaner/logging:common",
     "//chrome/chrome_cleaner/logging:scoped_timed_task_logger",
     "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
     "//chrome/chrome_cleaner/os:cleaner_os",
     "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/json_parser",
     "//chrome/chrome_cleaner/pup_data:pup_data_base",
     "//components/chrome_cleaner/public/constants:constants",
     "//components/crx_file",
@@ -58,11 +58,11 @@
     "//chrome/chrome_cleaner/constants:common_strings",
     "//chrome/chrome_cleaner/constants:uws_id",
     "//chrome/chrome_cleaner/http:mock_http_agent_factory",
-    "//chrome/chrome_cleaner/json_parser",
     "//chrome/chrome_cleaner/logging:cleaner_logging",
     "//chrome/chrome_cleaner/logging/proto:chrome_cleaner_report_proto",
     "//chrome/chrome_cleaner/os:cleaner_os",
     "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/json_parser",
     "//chrome/chrome_cleaner/test:test_branding_header",
     "//chrome/chrome_cleaner/test:test_component",
     "//chrome/chrome_cleaner/test:test_pup_data",
diff --git a/chrome/chrome_cleaner/components/system_report_component.cc b/chrome/chrome_cleaner/components/system_report_component.cc
index fb4245d..b1220c0 100644
--- a/chrome/chrome_cleaner/components/system_report_component.cc
+++ b/chrome/chrome_cleaner/components/system_report_component.cc
@@ -43,7 +43,6 @@
 #include "chrome/chrome_cleaner/chrome_utils/extensions_util.h"
 #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
 #include "chrome/chrome_cleaner/constants/common_registry_names.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_api.h"
 #include "chrome/chrome_cleaner/logging/logging_service_api.h"
 #include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
 #include "chrome/chrome_cleaner/logging/scoped_timed_task_logger.h"
@@ -59,6 +58,7 @@
 #include "chrome/chrome_cleaner/os/system_util.h"
 #include "chrome/chrome_cleaner/os/system_util_cleaner.h"
 #include "chrome/chrome_cleaner/os/task_scheduler.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 #include "components/chrome_cleaner/public/constants/constants.h"
 
 using base::WaitableEvent;
diff --git a/chrome/chrome_cleaner/components/system_report_component.h b/chrome/chrome_cleaner/components/system_report_component.h
index 4ec3665..e9d8366 100644
--- a/chrome/chrome_cleaner/components/system_report_component.h
+++ b/chrome/chrome_cleaner/components/system_report_component.h
@@ -8,7 +8,7 @@
 #include <vector>
 
 #include "chrome/chrome_cleaner/components/component_api.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_api.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 
 namespace chrome_cleaner {
 
diff --git a/chrome/chrome_cleaner/components/system_report_component_unittest.cc b/chrome/chrome_cleaner/components/system_report_component_unittest.cc
index e324477..927c1607 100644
--- a/chrome/chrome_cleaner/components/system_report_component_unittest.cc
+++ b/chrome/chrome_cleaner/components/system_report_component_unittest.cc
@@ -22,7 +22,6 @@
 #include "base/win/registry.h"
 #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
 #include "chrome/chrome_cleaner/constants/uws_id.h"
-#include "chrome/chrome_cleaner/json_parser/test_json_parser.h"
 #include "chrome/chrome_cleaner/logging/cleaner_logging_service.h"
 #include "chrome/chrome_cleaner/logging/logging_service_api.h"
 #include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
@@ -30,6 +29,7 @@
 #include "chrome/chrome_cleaner/os/file_path_sanitization.h"
 #include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
 #include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h"
 #include "chrome/chrome_cleaner/test/test_file_util.h"
 #include "chrome/chrome_cleaner/test/test_pup_data.h"
 #include "chrome/chrome_cleaner/test/test_util.h"
diff --git a/chrome/chrome_cleaner/crash/crashpad_crash_client.cc b/chrome/chrome_cleaner/crash/crashpad_crash_client.cc
index 189df2b..2058374 100644
--- a/chrome/chrome_cleaner/crash/crashpad_crash_client.cc
+++ b/chrome/chrome_cleaner/crash/crashpad_crash_client.cc
@@ -196,8 +196,8 @@
     case SandboxType::kEset:
       SetCrashKey(kProcessType, "eset");
       break;
-    case SandboxType::kJsonParser:
-      SetCrashKey(kProcessType, "json_parser");
+    case SandboxType::kParser:
+      SetCrashKey(kProcessType, "parser");
       break;
     case SandboxType::kZipArchiver:
       SetCrashKey(kProcessType, "zip_archiver");
diff --git a/chrome/chrome_cleaner/interfaces/BUILD.gn b/chrome/chrome_cleaner/interfaces/BUILD.gn
index be473325..3bf2b1f 100644
--- a/chrome/chrome_cleaner/interfaces/BUILD.gn
+++ b/chrome/chrome_cleaner/interfaces/BUILD.gn
@@ -31,9 +31,9 @@
   ]
 }
 
-chrome_cleaner_mojom("json_parser_interface") {
+chrome_cleaner_mojom("parser_interface") {
   sources = [
-    "json_parser.mojom",
+    "parser_interface.mojom",
   ]
   deps = [
     "//mojo/public/mojom/base",
diff --git a/chrome/chrome_cleaner/interfaces/json_parser.mojom b/chrome/chrome_cleaner/interfaces/json_parser.mojom
deleted file mode 100644
index c588cc15..0000000
--- a/chrome/chrome_cleaner/interfaces/json_parser.mojom
+++ /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.
-
-module chrome_cleaner.mojom;
-
-import "mojo/public/mojom/base/values.mojom";
-
-// Interface copied from services/data_decoder/public/mojom/json_parser.mojom,
-// which can't be used directly because it's closely tied to the service
-// manager which chrome_cleaner does not support.
-//
-// Sends a JSON string to parse from the high-privilege sandbox broker process
-// to a locked down sandbox target process where the parsing takes place.
-interface JsonParser {
-  // Parses |json| into a structured Value object. Returns the value in
-  // |result| if the parse was successful, or an error message in |error| if
-  // not.
-  Parse(string json) => (mojo_base.mojom.Value? result, string? error);
-};
diff --git a/chrome/chrome_cleaner/interfaces/parser_interface.mojom b/chrome/chrome_cleaner/interfaces/parser_interface.mojom
new file mode 100644
index 0000000..b5a9996
--- /dev/null
+++ b/chrome/chrome_cleaner/interfaces/parser_interface.mojom
@@ -0,0 +1,19 @@
+// 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 chrome_cleaner.mojom;
+
+import "mojo/public/mojom/base/values.mojom";
+
+// Common interface for the parsers used on the Chrome Cleanup Tool.
+interface Parser {
+  // JSON parser:
+  // Interface copied from services/data_decoder/public/mojom/json_parser.mojom,
+  // which can't be used directly because it's closely tied to the service
+  // manager which chrome_cleaner does not support.
+  //
+  // Sends a JSON string to parse from the high-privilege sandbox broker process
+  // to a locked down sandbox target process where the parsing takes place.
+  ParseJson(string json) => (mojo_base.mojom.Value? result, string? error);
+};
diff --git a/chrome/chrome_cleaner/ipc/sandbox.cc b/chrome/chrome_cleaner/ipc/sandbox.cc
index d3c4fa0..96da7fa 100644
--- a/chrome/chrome_cleaner/ipc/sandbox.cc
+++ b/chrome/chrome_cleaner/ipc/sandbox.cc
@@ -407,7 +407,9 @@
     case SandboxType::kEset:
       result_code = RESULT_CODE_ESET_SANDBOX_DISCONNECTED_TOO_SOON;
       break;
-    case SandboxType::kJsonParser:
+    case SandboxType::kParser:
+      // TODO(joenotcharles): This needs to be renamed to
+      // RESULT_CODE_PARSER_SANDBOX_DISCONNECTED_TOO_SOON.
       result_code = RESULT_CODE_JSON_PARSER_SANDBOX_DISCONNECTED_TOO_SOON;
       break;
     case SandboxType::kZipArchiver:
diff --git a/chrome/chrome_cleaner/json_parser/DEPS b/chrome/chrome_cleaner/json_parser/DEPS
deleted file mode 100644
index ef8ad28..0000000
--- a/chrome/chrome_cleaner/json_parser/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "+mojo/public",
-]
diff --git a/chrome/chrome_cleaner/json_parser/json_parser_impl.h b/chrome/chrome_cleaner/json_parser/json_parser_impl.h
deleted file mode 100644
index bead474..0000000
--- a/chrome/chrome_cleaner/json_parser/json_parser_impl.h
+++ /dev/null
@@ -1,28 +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_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_IMPL_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_IMPL_H_
-
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
-#include "mojo/public/cpp/bindings/binding.h"
-
-namespace chrome_cleaner {
-
-class JsonParserImpl : public mojom::JsonParser {
- public:
-  explicit JsonParserImpl(mojom::JsonParserRequest request,
-                          base::OnceClosure connection_error_handler);
-  ~JsonParserImpl() override;
-
-  // mojom::JsonParser
-  void Parse(const std::string& json, ParseCallback callback) override;
-
- private:
-  mojo::Binding<mojom::JsonParser> binding_;
-};
-
-}  // namespace chrome_cleaner
-
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_IMPL_H_
diff --git a/chrome/chrome_cleaner/json_parser/json_splicer.h b/chrome/chrome_cleaner/json_parser/json_splicer.h
deleted file mode 100644
index b5df59d..0000000
--- a/chrome/chrome_cleaner/json_parser/json_splicer.h
+++ /dev/null
@@ -1,29 +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_CHROME_CLEANER_JSON_PARSER_JSON_SPLICER_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_JSON_SPLICER_H_
-
-#include "base/values.h"
-
-namespace chrome_cleaner {
-
-class JsonSplicer {
- public:
-  ~JsonSplicer();
-
-  // Deletes the |key| entry from the |dictionary|.
-  //
-  // Returns true on success.
-  bool RemoveKeyFromDictionary(base::Value* dictionary, const std::string& key);
-
-  // Deletes the entry from the |list| with |key|.
-  //
-  // Returns true on success.
-  bool RemoveValueFromList(base::Value* list, const std::string& key);
-};
-
-}  // namespace chrome_cleaner
-
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_JSON_SPLICER_H_
diff --git a/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.cc b/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.cc
deleted file mode 100644
index 45e3098..0000000
--- a/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.cc
+++ /dev/null
@@ -1,65 +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/chrome_cleaner/json_parser/sandbox_setup_hooks.h"
-
-#include <utility>
-
-#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
-#include "chrome/chrome_cleaner/settings/settings_types.h"
-
-namespace chrome_cleaner {
-
-JsonParserSandboxSetupHooks::JsonParserSandboxSetupHooks(
-    scoped_refptr<MojoTaskRunner> mojo_task_runner,
-    base::OnceClosure connection_error_handler)
-    : mojo_task_runner_(mojo_task_runner),
-      connection_error_handler_(std::move(connection_error_handler)),
-      json_parser_ptr_(new mojom::JsonParserPtr(),
-                       base::OnTaskRunnerDeleter(mojo_task_runner_)) {}
-
-JsonParserSandboxSetupHooks::~JsonParserSandboxSetupHooks() = default;
-
-ResultCode JsonParserSandboxSetupHooks::UpdateSandboxPolicy(
-    sandbox::TargetPolicy* policy,
-    base::CommandLine* command_line) {
-  // Unretained reference is safe because the json_parser_ptr is taken by the
-  // caller and is expected to retain it for the life of the sandboxed process.
-  mojo_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&JsonParserSandboxSetupHooks::BindJsonParserPtr,
-                                base::Unretained(this),
-                                SetupSandboxMessagePipe(policy, command_line),
-                                base::Unretained(json_parser_ptr_.get())));
-
-  return RESULT_CODE_SUCCESS;
-}
-
-void JsonParserSandboxSetupHooks::BindJsonParserPtr(
-    mojo::ScopedMessagePipeHandle pipe_handle,
-    mojom::JsonParserPtr* json_parser_ptr) {
-  json_parser_ptr->Bind(mojom::JsonParserPtrInfo(std::move(pipe_handle), 0));
-  json_parser_ptr->set_connection_error_handler(
-      std::move(connection_error_handler_));
-}
-
-UniqueJsonParserPtr JsonParserSandboxSetupHooks::TakeJsonParserPtr() {
-  return std::move(json_parser_ptr_);
-}
-
-ResultCode SpawnJsonParserSandbox(
-    scoped_refptr<MojoTaskRunner> mojo_task_runner,
-    const SandboxConnectionErrorCallback& connection_error_callback,
-    UniqueJsonParserPtr* json_parser_ptr) {
-  // Call |connection_error_callback| with json parser sandbox type.
-  auto error_handler =
-      base::BindOnce(connection_error_callback, SandboxType::kJsonParser);
-  JsonParserSandboxSetupHooks setup_hooks(mojo_task_runner,
-                                          std::move(error_handler));
-  ResultCode result_code = SpawnSandbox(&setup_hooks, SandboxType::kJsonParser);
-  *json_parser_ptr = setup_hooks.TakeJsonParserPtr();
-
-  return result_code;
-}
-
-}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.h b/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.h
deleted file mode 100644
index 66e99442..0000000
--- a/chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.h
+++ /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.
-
-#ifndef CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_SETUP_HOOKS_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_SETUP_HOOKS_H_
-
-#include <memory>
-
-#include "base/command_line.h"
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
-#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
-#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/ipc/sandbox.h"
-#include "components/chrome_cleaner/public/constants/result_codes.h"
-#include "mojo/public/cpp/system/message_pipe.h"
-
-namespace chrome_cleaner {
-
-using UniqueJsonParserPtr =
-    std::unique_ptr<mojom::JsonParserPtr, base::OnTaskRunnerDeleter>;
-
-// Hooks to spawn a new sandboxed JSON parser process and bind a Mojo interface
-// pointer to the sandboxed implementation.
-class JsonParserSandboxSetupHooks : public MojoSandboxSetupHooks {
- public:
-  JsonParserSandboxSetupHooks(scoped_refptr<MojoTaskRunner> mojo_task_runner,
-                              base::OnceClosure connection_error_handler);
-  ~JsonParserSandboxSetupHooks() override;
-
-  // Transfers ownership of |json_parser_ptr_| to the caller.
-  UniqueJsonParserPtr TakeJsonParserPtr();
-
-  // SandboxSetupHooks
-  ResultCode UpdateSandboxPolicy(sandbox::TargetPolicy* policy,
-                                 base::CommandLine* command_line) override;
-
- private:
-  void BindJsonParserPtr(mojo::ScopedMessagePipeHandle pipe_handle,
-                         mojom::JsonParserPtr* json_parser_ptr);
-
-  scoped_refptr<MojoTaskRunner> mojo_task_runner_;
-  base::OnceClosure connection_error_handler_;
-
-  UniqueJsonParserPtr json_parser_ptr_;
-
-  DISALLOW_COPY_AND_ASSIGN(JsonParserSandboxSetupHooks);
-};
-
-// Spawn a sandboxed process with type kJsonParser, and return the bound
-// |json_parser_ptr|.
-ResultCode SpawnJsonParserSandbox(
-    scoped_refptr<MojoTaskRunner> mojo_task_runner,
-    const SandboxConnectionErrorCallback& connection_error_callback,
-    UniqueJsonParserPtr* json_parser_ptr);
-
-}  // namespace chrome_cleaner
-
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_SETUP_HOOKS_H_
diff --git a/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.cc b/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.cc
deleted file mode 100644
index 531265e..0000000
--- a/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.cc
+++ /dev/null
@@ -1,58 +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/chrome_cleaner/json_parser/sandbox_target_hooks.h"
-
-#include <utility>
-
-#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
-#include "chrome/chrome_cleaner/os/early_exit.h"
-
-namespace chrome_cleaner {
-
-JsonParserSandboxTargetHooks::JsonParserSandboxTargetHooks(
-    MojoTaskRunner* mojo_task_runner)
-    : mojo_task_runner_(mojo_task_runner) {}
-
-JsonParserSandboxTargetHooks::~JsonParserSandboxTargetHooks() {
-  // Ensure Mojo objects are deleted on the IPC thread.
-  mojo_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](std::unique_ptr<JsonParserImpl> json_parser_impl) {
-                       json_parser_impl.reset();
-                     },
-                     base::Passed(&json_parser_impl_)));
-}
-
-ResultCode JsonParserSandboxTargetHooks::TargetDroppedPrivileges(
-    const base::CommandLine& command_line) {
-  mojom::JsonParserRequest request(ExtractSandboxMessagePipe(command_line));
-
-  // This loop will run forever. Once the communication channel with the broker
-  // process is broken, mojo error handler will abort this process.
-  base::RunLoop run_loop;
-  mojo_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&JsonParserSandboxTargetHooks::CreateJsonParserImpl,
-                     base::Unretained(this), base::Passed(&request)));
-  run_loop.Run();
-
-  return RESULT_CODE_SUCCESS;
-}
-
-void JsonParserSandboxTargetHooks::CreateJsonParserImpl(
-    mojom::JsonParserRequest request) {
-  json_parser_impl_ = std::make_unique<JsonParserImpl>(
-      std::move(request), base::BindOnce(&EarlyExit, 1));
-}
-
-ResultCode RunJsonParserSandbox(const base::CommandLine& command_line,
-                                sandbox::TargetServices* target_services) {
-  scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
-  JsonParserSandboxTargetHooks target_hooks(mojo_task_runner.get());
-
-  return RunSandboxTarget(command_line, target_services, &target_hooks);
-}
-
-}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.h b/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.h
deleted file mode 100644
index cf97c94..0000000
--- a/chrome/chrome_cleaner/json_parser/sandbox_target_hooks.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_TARGET_HOOKS_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_TARGET_HOOKS_H_
-
-#include <memory>
-
-#include "base/command_line.h"
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
-#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
-#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_impl.h"
-#include "components/chrome_cleaner/public/constants/result_codes.h"
-
-namespace chrome_cleaner {
-
-// Hooks to initialize the sandboxed JSON parser process by creating a
-// JsonParserImpl instance and binding it to the mojo pipe.
-class JsonParserSandboxTargetHooks : public MojoSandboxTargetHooks {
- public:
-  explicit JsonParserSandboxTargetHooks(MojoTaskRunner* mojo_task_runner);
-  ~JsonParserSandboxTargetHooks() override;
-
-  // SandboxTargetHooks
-  ResultCode TargetDroppedPrivileges(
-      const base::CommandLine& command_line) override;
-
- private:
-  void CreateJsonParserImpl(mojom::JsonParserRequest request);
-
-  MojoTaskRunner* mojo_task_runner_;
-  base::MessageLoop message_loop_;
-  std::unique_ptr<JsonParserImpl> json_parser_impl_;
-
-  DISALLOW_COPY_AND_ASSIGN(JsonParserSandboxTargetHooks);
-};
-
-// Run the JSON parser, to be called in a newly spawned process.
-ResultCode RunJsonParserSandbox(const base::CommandLine& command_line,
-                                sandbox::TargetServices* target_services);
-
-}  // namespace chrome_cleaner
-
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOX_TARGET_HOOKS_H_
diff --git a/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.cc b/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.cc
deleted file mode 100644
index a741bd51c..0000000
--- a/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.cc
+++ /dev/null
@@ -1,24 +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/chrome_cleaner/json_parser/sandboxed_json_parser.h"
-
-namespace chrome_cleaner {
-
-SandboxedJsonParser::SandboxedJsonParser(MojoTaskRunner* mojo_task_runner,
-                                         mojom::JsonParserPtr* json_parser_ptr)
-    : mojo_task_runner_(mojo_task_runner), json_parser_ptr_(json_parser_ptr) {}
-
-void SandboxedJsonParser::Parse(const std::string& json,
-                                ParseDoneCallback callback) {
-  mojo_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](mojom::JsonParserPtr* json_parser_ptr,
-                        const std::string& json, ParseDoneCallback callback) {
-                       (*json_parser_ptr)->Parse(json, std::move(callback));
-                     },
-                     json_parser_ptr_, json, std::move(callback)));
-}
-
-}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.h b/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.h
deleted file mode 100644
index 0ddcae4..0000000
--- a/chrome/chrome_cleaner/json_parser/sandboxed_json_parser.h
+++ /dev/null
@@ -1,29 +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_CHROME_CLEANER_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
-
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
-#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_api.h"
-
-namespace chrome_cleaner {
-
-// An implementation of JsonParserAPI to wrap a MojoTaskRunner and
-// JsonParserPtr. Parses via |json_parser_ptr_| on the |mojo_task_runner_|.
-class SandboxedJsonParser : public JsonParserAPI {
- public:
-  SandboxedJsonParser(MojoTaskRunner* mojo_task_runner,
-                      mojom::JsonParserPtr* json_parser_ptr);
-  void Parse(const std::string& json, ParseDoneCallback callback) override;
-
- private:
-  MojoTaskRunner* mojo_task_runner_;
-  mojom::JsonParserPtr* json_parser_ptr_;
-};
-
-}  // namespace chrome_cleaner
-
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
diff --git a/chrome/chrome_cleaner/logging/proto/shared_data.proto b/chrome/chrome_cleaner/logging/proto/shared_data.proto
index ae14684..3a15198a 100644
--- a/chrome/chrome_cleaner/logging/proto/shared_data.proto
+++ b/chrome/chrome_cleaner/logging/proto/shared_data.proto
@@ -266,7 +266,7 @@
     MAIN = 1;
     DEPRECATED_SIGNATURE_MATCHER_SANDBOX = 2;
     ESET_SANDBOX = 3;
-    JSON_PARSER_SANDBOX = 4;
+    PARSER_SANDBOX = 4;
     ZIP_ARCHIVER_SANDBOX = 5;
   }
   optional Process process = 1;
diff --git a/chrome/chrome_cleaner/logging/utils.cc b/chrome/chrome_cleaner/logging/utils.cc
index 032125d..d96439c 100644
--- a/chrome/chrome_cleaner/logging/utils.cc
+++ b/chrome/chrome_cleaner/logging/utils.cc
@@ -252,8 +252,8 @@
     case SandboxType::kEset:
       process_info.set_process(ProcessInformation::ESET_SANDBOX);
       break;
-    case SandboxType::kJsonParser:
-      process_info.set_process(ProcessInformation::JSON_PARSER_SANDBOX);
+    case SandboxType::kParser:
+      process_info.set_process(ProcessInformation::PARSER_SANDBOX);
       break;
     case SandboxType::kZipArchiver:
       process_info.set_process(ProcessInformation::ZIP_ARCHIVER_SANDBOX);
diff --git a/chrome/chrome_cleaner/parsers/DEPS b/chrome/chrome_cleaner/parsers/DEPS
new file mode 100644
index 0000000..093b1d9
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+mojo/public/cpp",
+]
diff --git a/chrome/chrome_cleaner/parsers/broker/BUILD.gn b/chrome/chrome_cleaner/parsers/broker/BUILD.gn
new file mode 100644
index 0000000..a79e3432
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/broker/BUILD.gn
@@ -0,0 +1,43 @@
+# 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.
+
+static_library("parser_sandbox_broker") {
+  sources = [
+    "sandbox_setup_hooks.cc",
+    "sandbox_setup_hooks.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
+    "//chrome/chrome_cleaner/ipc:mojo_task_runner",
+    "//chrome/chrome_cleaner/ipc:sandbox",
+    "//chrome/chrome_cleaner/settings:settings_types",
+    "//components/chrome_cleaner/public/constants:constants",
+    "//mojo/public/cpp/bindings",
+  ]
+}
+
+source_set("unittest_sources") {
+  testonly = true
+
+  sources = [
+    "json_parser_sandbox_setup_unittest.cc",
+  ]
+  deps = [
+    ":parser_sandbox_broker",
+    "//base:base",
+    "//base/test:test_support",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
+    "//chrome/chrome_cleaner/ipc:mojo_task_runner",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/json_parser:json_parser",
+    "//chrome/chrome_cleaner/parsers/json_parser:json_splicer",
+    "//chrome/chrome_cleaner/parsers/target:parser_sandbox_target",
+    "//mojo/public/cpp/bindings:bindings",
+    "//sandbox/win:sandbox",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/chrome_cleaner/json_parser/sandbox_setup_unittest.cc b/chrome/chrome_cleaner/parsers/broker/json_parser_sandbox_setup_unittest.cc
similarity index 62%
rename from chrome/chrome_cleaner/json_parser/sandbox_setup_unittest.cc
rename to chrome/chrome_cleaner/parsers/broker/json_parser_sandbox_setup_unittest.cc
index 88a634c..599b574 100644
--- a/chrome/chrome_cleaner/json_parser/sandbox_setup_unittest.cc
+++ b/chrome/chrome_cleaner/parsers/broker/json_parser_sandbox_setup_unittest.cc
@@ -2,15 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/memory/scoped_refptr.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_timeouts.h"
 #include "base/time/time.h"
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
 #include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/json_parser/sandbox_setup_hooks.h"
-#include "chrome/chrome_cleaner/json_parser/sandbox_target_hooks.h"
+#include "chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.h"
+#include "chrome/chrome_cleaner/parsers/target/sandbox_setup.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
 #include "sandbox/win/src/sandbox_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "testing/multiprocess_func_list.h"
 
 using base::WaitableEvent;
@@ -19,31 +22,30 @@
 
 namespace {
 
-const char kTestJsonKey[] = "name";
-const char kTestJsonValue[] = "Jason";
-const char kTestJsonText[] = "{ \"name\": \"Jason\" }";
-const char kInvalidJsonText[] = "{ name: jason }";
+const char kTestKey[] = "name";
+const char kTestValue[] = "Jason";
+const char kTestText[] = "{ \"name\": \"Jason\" }";
+const char kInvalidText[] = "{ name: jason }";
 
 class JsonParserSandboxSetupTest : public base::MultiProcessTest {
  public:
   JsonParserSandboxSetupTest()
-      : json_parser_ptr_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {}
+      : parser_ptr_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {}
 
   void SetUp() override {
     mojo_task_runner_ = MojoTaskRunner::Create();
-    JsonParserSandboxSetupHooks setup_hooks(
-        mojo_task_runner_.get(), base::BindOnce([] {
-          FAIL() << "JsonParser sandbox connection error";
-        }));
+    ParserSandboxSetupHooks setup_hooks(
+        mojo_task_runner_.get(),
+        base::BindOnce([] { FAIL() << "Parser sandbox connection error"; }));
     ASSERT_EQ(RESULT_CODE_SUCCESS,
               StartSandboxTarget(MakeCmdLine("JsonParserSandboxTargetMain"),
                                  &setup_hooks, SandboxType::kTest));
-    json_parser_ptr_ = setup_hooks.TakeJsonParserPtr();
+    parser_ptr_ = setup_hooks.TakeParserPtr();
   }
 
  protected:
   scoped_refptr<MojoTaskRunner> mojo_task_runner_;
-  UniqueJsonParserPtr json_parser_ptr_;
+  UniqueParserPtr parser_ptr_;
 };
 
 void ParseCallbackExpectedKeyValue(const std::string& expected_key,
@@ -79,26 +81,26 @@
   CHECK(sandbox_target_services);
 
   EXPECT_EQ(RESULT_CODE_SUCCESS,
-            RunJsonParserSandbox(*base::CommandLine::ForCurrentProcess(),
-                                 sandbox_target_services));
+            RunParserSandboxTarget(*base::CommandLine::ForCurrentProcess(),
+                                   sandbox_target_services));
 
   return ::testing::Test::HasNonfatalFailure();
 }
 
-TEST_F(JsonParserSandboxSetupTest, ParseJsonSandboxed) {
+TEST_F(JsonParserSandboxSetupTest, ParseValidJsonSandboxed) {
   WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
                      WaitableEvent::InitialState::NOT_SIGNALED);
 
   mojo_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](mojom::JsonParserPtr* json_parser_ptr, WaitableEvent* done) {
-            (*json_parser_ptr)
-                ->Parse(kTestJsonText,
-                        base::BindOnce(&ParseCallbackExpectedKeyValue,
-                                       kTestJsonKey, kTestJsonValue, done));
-          },
-          json_parser_ptr_.get(), &done));
+      FROM_HERE, base::BindOnce(
+                     [](mojom::ParserPtr* parser_ptr, WaitableEvent* done) {
+                       (*parser_ptr)
+                           ->ParseJson(
+                               kTestText,
+                               base::BindOnce(&ParseCallbackExpectedKeyValue,
+                                              kTestKey, kTestValue, done));
+                     },
+                     parser_ptr_.get(), &done));
   EXPECT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
 }
 
@@ -109,12 +111,12 @@
   mojo_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](mojom::JsonParserPtr* json_parser_ptr, WaitableEvent* done) {
-            (*json_parser_ptr)
-                ->Parse(kInvalidJsonText,
-                        base::BindOnce(&ParseCallbackExpectedError, done));
+          [](mojom::ParserPtr* parser_ptr, WaitableEvent* done) {
+            (*parser_ptr)
+                ->ParseJson(kInvalidText,
+                            base::BindOnce(&ParseCallbackExpectedError, done));
           },
-          json_parser_ptr_.get(), &done));
+          parser_ptr_.get(), &done));
   EXPECT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
 }
 
diff --git a/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.cc b/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.cc
new file mode 100644
index 0000000..ef7e8b3b
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.cc
@@ -0,0 +1,64 @@
+// 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/chrome_cleaner/parsers/broker/sandbox_setup_hooks.h"
+
+#include <utility>
+
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/settings/settings_types.h"
+
+namespace chrome_cleaner {
+
+ParserSandboxSetupHooks::ParserSandboxSetupHooks(
+    scoped_refptr<MojoTaskRunner> mojo_task_runner,
+    base::OnceClosure connection_error_handler)
+    : mojo_task_runner_(mojo_task_runner),
+      connection_error_handler_(std::move(connection_error_handler)),
+      parser_ptr_(new mojom::ParserPtr(),
+                  base::OnTaskRunnerDeleter(mojo_task_runner_)) {}
+
+ParserSandboxSetupHooks::~ParserSandboxSetupHooks() = default;
+
+ResultCode ParserSandboxSetupHooks::UpdateSandboxPolicy(
+    sandbox::TargetPolicy* policy,
+    base::CommandLine* command_line) {
+  // Unretained reference is safe because the parser_ptr is taken by the
+  // caller and is expected to retain it for the life of the sandboxed process.
+  mojo_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&ParserSandboxSetupHooks::BindParserPtr,
+                                base::Unretained(this),
+                                SetupSandboxMessagePipe(policy, command_line),
+                                base::Unretained(parser_ptr_.get())));
+
+  return RESULT_CODE_SUCCESS;
+}
+
+void ParserSandboxSetupHooks::BindParserPtr(
+    mojo::ScopedMessagePipeHandle pipe_handle,
+    mojom::ParserPtr* parser_ptr) {
+  parser_ptr->Bind(mojom::ParserPtrInfo(std::move(pipe_handle), 0));
+  parser_ptr->set_connection_error_handler(
+      std::move(connection_error_handler_));
+}
+
+UniqueParserPtr ParserSandboxSetupHooks::TakeParserPtr() {
+  return std::move(parser_ptr_);
+}
+
+ResultCode SpawnParserSandbox(
+    scoped_refptr<MojoTaskRunner> mojo_task_runner,
+    const SandboxConnectionErrorCallback& connection_error_callback,
+    UniqueParserPtr* parser_ptr) {
+  auto error_handler =
+      base::BindOnce(connection_error_callback, SandboxType::kParser);
+  ParserSandboxSetupHooks setup_hooks(mojo_task_runner,
+                                      std::move(error_handler));
+  ResultCode result_code = SpawnSandbox(&setup_hooks, SandboxType::kParser);
+  *parser_ptr = setup_hooks.TakeParserPtr();
+
+  return result_code;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.h b/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.h
new file mode 100644
index 0000000..e41da15
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/broker/sandbox_setup_hooks.h
@@ -0,0 +1,59 @@
+// 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_CHROME_CLEANER_PARSERS_BROKER_SANDBOX_SETUP_HOOKS_H_
+#define CHROME_CHROME_CLEANER_PARSERS_BROKER_SANDBOX_SETUP_HOOKS_H_
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
+#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
+#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
+#include "chrome/chrome_cleaner/ipc/sandbox.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace chrome_cleaner {
+
+using UniqueParserPtr =
+    std::unique_ptr<mojom::ParserPtr, base::OnTaskRunnerDeleter>;
+
+// Hooks to spawn a new sandboxed Parser process and bind a Mojo interface
+// pointer to the sandboxed implementation.
+class ParserSandboxSetupHooks : public MojoSandboxSetupHooks {
+ public:
+  ParserSandboxSetupHooks(scoped_refptr<MojoTaskRunner> mojo_task_runner,
+                          base::OnceClosure connection_error_handler);
+  ~ParserSandboxSetupHooks() override;
+
+  // Transfers ownership of |parser_ptr_| to the caller.
+  UniqueParserPtr TakeParserPtr();
+
+  // SandboxSetupHooks
+  ResultCode UpdateSandboxPolicy(sandbox::TargetPolicy* policy,
+                                 base::CommandLine* command_line) override;
+
+ private:
+  void BindParserPtr(mojo::ScopedMessagePipeHandle pipe_handle,
+                     mojom::ParserPtr* parser_ptr);
+
+  scoped_refptr<MojoTaskRunner> mojo_task_runner_;
+  base::OnceClosure connection_error_handler_;
+
+  UniqueParserPtr parser_ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(ParserSandboxSetupHooks);
+};
+
+// Spawn a sandboxed process with type kParser, and return the bound
+// |parser_ptr|.
+ResultCode SpawnParserSandbox(
+    scoped_refptr<MojoTaskRunner> mojo_task_runner,
+    const SandboxConnectionErrorCallback& connection_error_callback,
+    UniqueParserPtr* parser_ptr);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_BROKER_SANDBOX_SETUP_HOOKS_H_
diff --git a/chrome/chrome_cleaner/json_parser/BUILD.gn b/chrome/chrome_cleaner/parsers/json_parser/BUILD.gn
similarity index 75%
rename from chrome/chrome_cleaner/json_parser/BUILD.gn
rename to chrome/chrome_cleaner/parsers/json_parser/BUILD.gn
index eb75e9f..0d9420db 100644
--- a/chrome/chrome_cleaner/json_parser/BUILD.gn
+++ b/chrome/chrome_cleaner/parsers/json_parser/BUILD.gn
@@ -16,12 +16,6 @@
 static_library("json_parser") {
   sources = [
     "json_parser_api.h",
-    "json_parser_impl.cc",
-    "json_parser_impl.h",
-    "sandbox_setup_hooks.cc",
-    "sandbox_setup_hooks.h",
-    "sandbox_target_hooks.cc",
-    "sandbox_target_hooks.h",
     "sandboxed_json_parser.cc",
     "sandboxed_json_parser.h",
     "test_json_parser.cc",
@@ -31,10 +25,12 @@
   deps = [
     "//base:base",
     "//chrome/chrome_cleaner/constants:common_strings",
-    "//chrome/chrome_cleaner/interfaces:json_parser_interface",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
     "//chrome/chrome_cleaner/ipc:mojo_task_runner",
     "//chrome/chrome_cleaner/ipc:sandbox",
     "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/broker:parser_sandbox_broker",
+    "//chrome/chrome_cleaner/parsers/target:parser_sandbox_target",
     "//chrome/chrome_cleaner/settings:settings_types",
     "//components/chrome_cleaner/public/constants",
     "//mojo/public/cpp/bindings",
@@ -45,9 +41,7 @@
   testonly = true
 
   sources = [
-    "json_parser_impl_unittest.cc",
     "json_splicer_unittest.cc",
-    "sandbox_setup_unittest.cc",
   ]
 
   deps = [
@@ -55,8 +49,10 @@
     ":json_splicer",
     "//base:base",
     "//base/test:test_support",
-    "//chrome/chrome_cleaner/interfaces:json_parser_interface",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
     "//chrome/chrome_cleaner/ipc:mojo_task_runner",
+    "//chrome/chrome_cleaner/parsers/broker:parser_sandbox_broker",
+    "//chrome/chrome_cleaner/parsers/target:parser_sandbox_target",
     "//mojo/public/cpp/bindings",
     "//sandbox/win:sandbox",
     "//testing/gtest",
diff --git a/chrome/chrome_cleaner/json_parser/json_parser_api.h b/chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h
similarity index 74%
rename from chrome/chrome_cleaner/json_parser/json_parser_api.h
rename to chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h
index 1d106dd..487e7f1 100644
--- a/chrome/chrome_cleaner/json_parser/json_parser_api.h
+++ b/chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_API_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_API_H_
+#ifndef CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_PARSER_API_H_
+#define CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_PARSER_API_H_
 
 #include "base/callback.h"
 #include "base/optional.h"
@@ -24,4 +24,4 @@
 
 }  // namespace chrome_cleaner
 
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_JSON_PARSER_API_H_
+#endif  // CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_PARSER_API_H_
diff --git a/chrome/chrome_cleaner/json_parser/json_splicer.cc b/chrome/chrome_cleaner/parsers/json_parser/json_splicer.cc
similarity index 77%
rename from chrome/chrome_cleaner/json_parser/json_splicer.cc
rename to chrome/chrome_cleaner/parsers/json_parser/json_splicer.cc
index 3bdbf6b..c3fb8ad 100644
--- a/chrome/chrome_cleaner/json_parser/json_splicer.cc
+++ b/chrome/chrome_cleaner/parsers/json_parser/json_splicer.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/chrome_cleaner/json_parser/json_splicer.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_splicer.h"
+
+#include <string>
+#include <vector>
 
 #include "base/values.h"
 
 namespace chrome_cleaner {
 
-JsonSplicer::~JsonSplicer() = default;
-
-bool JsonSplicer::RemoveKeyFromDictionary(base::Value* dictionary,
-                                          const std::string& key) {
+bool RemoveKeyFromDictionary(base::Value* dictionary, const std::string& key) {
   bool result = false;
   base::DictionaryValue* entries = nullptr;
   if (dictionary == nullptr || !dictionary->is_dict() ||
@@ -28,8 +28,7 @@
   return result;
 }
 
-bool JsonSplicer::RemoveValueFromList(base::Value* list,
-                                      const std::string& key) {
+bool RemoveValueFromList(base::Value* list, const std::string& key) {
   if (list == nullptr || !list->is_list()) {
     LOG(ERROR) << "Got a " << (list ? list->GetTypeName(list->type()) : "NULL")
                << " but expected a list.";
diff --git a/chrome/chrome_cleaner/parsers/json_parser/json_splicer.h b/chrome/chrome_cleaner/parsers/json_parser/json_splicer.h
new file mode 100644
index 0000000..eeb7170b
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/json_parser/json_splicer.h
@@ -0,0 +1,26 @@
+// 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_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_SPLICER_H_
+#define CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_SPLICER_H_
+
+#include <string>
+
+#include "base/values.h"
+
+namespace chrome_cleaner {
+
+// Deletes the |key| entry from the |dictionary|.
+//
+// Returns true on success.
+bool RemoveKeyFromDictionary(base::Value* dictionary, const std::string& key);
+
+// Deletes the entry from the |list| with |key|.
+//
+// Returns true on success.
+bool RemoveValueFromList(base::Value* list, const std::string& key);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_JSON_SPLICER_H_
diff --git a/chrome/chrome_cleaner/json_parser/json_splicer_unittest.cc b/chrome/chrome_cleaner/parsers/json_parser/json_splicer_unittest.cc
similarity index 77%
rename from chrome/chrome_cleaner/json_parser/json_splicer_unittest.cc
rename to chrome/chrome_cleaner/parsers/json_parser/json_splicer_unittest.cc
index 069cf7f6..ac081b1 100644
--- a/chrome/chrome_cleaner/json_parser/json_splicer_unittest.cc
+++ b/chrome/chrome_cleaner/parsers/json_parser/json_splicer_unittest.cc
@@ -2,16 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/chrome_cleaner/json_parser/json_splicer.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_splicer.h"
+
+#include <memory>
+#include <string>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/test/test_timeouts.h"
 #include "base/values.h"
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
 #include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_impl.h"
-#include "chrome/chrome_cleaner/json_parser/sandboxed_json_parser.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h"
+#include "chrome/chrome_cleaner/parsers/target/parser_impl.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -57,28 +61,25 @@
  public:
   JsonSplicerImplTest()
       : task_runner_(MojoTaskRunner::Create()),
-        json_parser_ptr_(new mojom::JsonParserPtr(),
-                         base::OnTaskRunnerDeleter(task_runner_)),
-        json_parser_impl_(nullptr, base::OnTaskRunnerDeleter(task_runner_)),
-        sandboxed_json_parser_(task_runner_.get(), json_parser_ptr_.get()) {
+        parser_ptr_(new mojom::ParserPtr(),
+                    base::OnTaskRunnerDeleter(task_runner_)),
+        parser_impl_(nullptr, base::OnTaskRunnerDeleter(task_runner_)),
+        sandboxed_json_parser_(task_runner_.get(), parser_ptr_.get()) {
     task_runner_->PostTask(
-        FROM_HERE,
-        BindOnce(BindParser, json_parser_ptr_.get(), &json_parser_impl_));
+        FROM_HERE, BindOnce(BindParser, parser_ptr_.get(), &parser_impl_));
   }
 
  protected:
   static void BindParser(
-      mojom::JsonParserPtr* json_parser,
-      std::unique_ptr<JsonParserImpl, base::OnTaskRunnerDeleter>*
-          json_parser_impl) {
-    json_parser_impl->reset(
-        new JsonParserImpl(mojo::MakeRequest(json_parser), base::DoNothing()));
+      mojom::ParserPtr* json_parser,
+      std::unique_ptr<ParserImpl, base::OnTaskRunnerDeleter>* parser_impl) {
+    parser_impl->reset(
+        new ParserImpl(mojo::MakeRequest(json_parser), base::DoNothing()));
   }
 
   scoped_refptr<MojoTaskRunner> task_runner_;
-  std::unique_ptr<mojom::JsonParserPtr, base::OnTaskRunnerDeleter>
-      json_parser_ptr_;
-  std::unique_ptr<JsonParserImpl, base::OnTaskRunnerDeleter> json_parser_impl_;
+  std::unique_ptr<mojom::ParserPtr, base::OnTaskRunnerDeleter> parser_ptr_;
+  std::unique_ptr<ParserImpl, base::OnTaskRunnerDeleter> parser_impl_;
   SandboxedJsonParser sandboxed_json_parser_;
 };
 
@@ -92,17 +93,16 @@
       base::BindOnce(
           [](base::WaitableEvent* done, base::Optional<base::Value> value,
              const base::Optional<std::string>& error) {
-            JsonSplicer splicer;
             ASSERT_FALSE(error.has_value());
             ASSERT_TRUE(value.has_value());
             base::DictionaryValue* dict;
             ASSERT_TRUE(value->GetAsDictionary(&dict));
             ASSERT_TRUE(IsDaysOfWeek(dict));
             std::string blank = "";
-            ASSERT_FALSE(splicer.RemoveKeyFromDictionary(dict, blank));
+            ASSERT_FALSE(RemoveKeyFromDictionary(dict, blank));
             ASSERT_TRUE(IsDaysOfWeek(dict));
             std::string random = "aoeu";
-            ASSERT_FALSE(splicer.RemoveKeyFromDictionary(dict, random));
+            ASSERT_FALSE(RemoveKeyFromDictionary(dict, random));
             ASSERT_TRUE(IsDaysOfWeek(dict));
             done->Signal();
           },
@@ -118,7 +118,6 @@
       base::BindOnce(
           [](base::WaitableEvent* done, base::Optional<base::Value> value,
              const base::Optional<std::string>& error) {
-            JsonSplicer splicer;
             ASSERT_FALSE(error.has_value());
             ASSERT_TRUE(value.has_value());
             base::DictionaryValue* dict;
@@ -127,13 +126,13 @@
 
             std::string monday = "monday";
             ASSERT_TRUE(dict->HasKey(monday));
-            ASSERT_TRUE(splicer.RemoveKeyFromDictionary(dict, monday));
+            ASSERT_TRUE(RemoveKeyFromDictionary(dict, monday));
             ASSERT_FALSE(IsDaysOfWeek(dict));
             ASSERT_FALSE(dict->HasKey(monday));
 
             std::string wednesday = "wednesday";
             ASSERT_TRUE(dict->HasKey(wednesday));
-            ASSERT_TRUE(splicer.RemoveKeyFromDictionary(dict, wednesday));
+            ASSERT_TRUE(RemoveKeyFromDictionary(dict, wednesday));
             ASSERT_FALSE(IsDaysOfWeek(dict));
             ASSERT_FALSE(dict->HasKey(wednesday));
             done->Signal();
@@ -150,16 +149,15 @@
       base::BindOnce(
           [](base::WaitableEvent* done, base::Optional<base::Value> value,
              const base::Optional<std::string>& error) {
-            JsonSplicer splicer;
             ASSERT_FALSE(error.has_value());
             ASSERT_TRUE(value.has_value());
             std::vector<base::Value>& list = value->GetList();
             ASSERT_TRUE(IsDaysOfWeek(list));
             std::string blank = "";
-            ASSERT_FALSE(splicer.RemoveValueFromList(&*value, blank));
+            ASSERT_FALSE(RemoveValueFromList(&*value, blank));
             ASSERT_TRUE(IsDaysOfWeek(list));
             std::string random = "aoeu";
-            ASSERT_FALSE(splicer.RemoveValueFromList(&*value, random));
+            ASSERT_FALSE(RemoveValueFromList(&*value, random));
             ASSERT_TRUE(IsDaysOfWeek(list));
             done->Signal();
           },
@@ -175,18 +173,17 @@
       base::BindOnce(
           [](base::WaitableEvent* done, base::Optional<base::Value> value,
              const base::Optional<std::string>& error) {
-            JsonSplicer splicer;
             ASSERT_FALSE(error.has_value());
             ASSERT_TRUE(value.has_value());
             std::vector<base::Value>& list = value->GetList();
             ASSERT_TRUE(IsDaysOfWeek(list));
             std::string monday = "monday";
-            ASSERT_TRUE(splicer.RemoveValueFromList(&*value, monday));
+            ASSERT_TRUE(RemoveValueFromList(&*value, monday));
             ASSERT_FALSE(IsDaysOfWeek(list));
             ASSERT_FALSE(std::find(list.begin(), list.end(),
                                    base::Value(monday)) != list.end());
             std::string wednesday = "wednesday";
-            ASSERT_TRUE(splicer.RemoveValueFromList(&*value, wednesday));
+            ASSERT_TRUE(RemoveValueFromList(&*value, wednesday));
             ASSERT_FALSE(IsDaysOfWeek(list));
             ASSERT_FALSE(std::find(list.begin(), list.end(),
                                    base::Value(monday)) != list.end());
diff --git a/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.cc b/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.cc
new file mode 100644
index 0000000..a60091d
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.cc
@@ -0,0 +1,24 @@
+// 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/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h"
+
+namespace chrome_cleaner {
+
+SandboxedJsonParser::SandboxedJsonParser(MojoTaskRunner* mojo_task_runner,
+                                         mojom::ParserPtr* parser_ptr)
+    : mojo_task_runner_(mojo_task_runner), parser_ptr_(parser_ptr) {}
+
+void SandboxedJsonParser::Parse(const std::string& json,
+                                ParseDoneCallback callback) {
+  mojo_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](mojom::ParserPtr* parser_ptr, const std::string& json,
+                        ParseDoneCallback callback) {
+                       (*parser_ptr)->ParseJson(json, std::move(callback));
+                     },
+                     parser_ptr_, json, std::move(callback)));
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h b/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h
new file mode 100644
index 0000000..5d348df
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h
@@ -0,0 +1,30 @@
+// 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_CHROME_CLEANER_PARSERS_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
+#define CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
+
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
+#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
+
+namespace chrome_cleaner {
+
+// An implementation of JsonParserAPI to wrap a MojoTaskRunner and
+// JsonParserPtr. Parses via |parser_ptr_| on the |mojo_task_runner_|.
+// TODO(joenotcharles): Move this class to chrome_cleaner/parsers/broker.
+class SandboxedJsonParser : public JsonParserAPI {
+ public:
+  SandboxedJsonParser(MojoTaskRunner* mojo_task_runner,
+                      mojom::ParserPtr* parser_ptr);
+  void Parse(const std::string& json, ParseDoneCallback callback) override;
+
+ private:
+  MojoTaskRunner* mojo_task_runner_;
+  mojom::ParserPtr* parser_ptr_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_SANDBOXED_JSON_PARSER_H_
diff --git a/chrome/chrome_cleaner/json_parser/test_json_parser.cc b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
similarity index 86%
rename from chrome/chrome_cleaner/json_parser/test_json_parser.cc
rename to chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
index 34cb6ac..43a205f4 100644
--- a/chrome/chrome_cleaner/json_parser/test_json_parser.cc
+++ b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/chrome_cleaner/json_parser/test_json_parser.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h"
 
 #include "base/json/json_reader.h"
 #include "base/values.h"
-#include "chrome/chrome_cleaner/json_parser/json_parser_impl.h"
+#include "chrome/chrome_cleaner/parsers/target/parser_impl.h"
 
 namespace chrome_cleaner {
 
diff --git a/chrome/chrome_cleaner/json_parser/test_json_parser.h b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h
similarity index 66%
rename from chrome/chrome_cleaner/json_parser/test_json_parser.h
rename to chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h
index 3e9fa11..c6e5a94 100644
--- a/chrome/chrome_cleaner/json_parser/test_json_parser.h
+++ b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.h
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_CHROME_CLEANER_JSON_PARSER_TEST_JSON_PARSER_H_
-#define CHROME_CHROME_CLEANER_JSON_PARSER_TEST_JSON_PARSER_H_
+#ifndef CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_TEST_JSON_PARSER_H_
+#define CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_TEST_JSON_PARSER_H_
 
-#include "chrome/chrome_cleaner/json_parser/json_parser_api.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/json_parser_api.h"
 
 namespace chrome_cleaner {
 
@@ -19,4 +19,4 @@
 
 }  // namespace chrome_cleaner
 
-#endif  // CHROME_CHROME_CLEANER_JSON_PARSER_TEST_JSON_PARSER_H_
+#endif  // CHROME_CHROME_CLEANER_PARSERS_JSON_PARSER_TEST_JSON_PARSER_H_
diff --git a/chrome/chrome_cleaner/parsers/target/BUILD.gn b/chrome/chrome_cleaner/parsers/target/BUILD.gn
new file mode 100644
index 0000000..4bfffe8
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/target/BUILD.gn
@@ -0,0 +1,45 @@
+# 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.
+
+static_library("parser_sandbox_target") {
+  sources = [
+    "parser_impl.cc",
+    "parser_impl.h",
+    "sandbox_setup.cc",
+    "sandbox_setup.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
+    "//chrome/chrome_cleaner/ipc:mojo_task_runner",
+    "//chrome/chrome_cleaner/ipc:sandbox",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/settings:settings_types",
+    "//components/chrome_cleaner/public/constants:constants",
+    "//mojo/public/cpp/bindings",
+    "//sandbox/win:sandbox",
+  ]
+}
+
+source_set("unittest_sources") {
+  testonly = true
+
+  sources = [
+    "parser_impl_unittest.cc",
+  ]
+  deps = [
+    ":parser_sandbox_target",
+    "//base:base",
+    "//base/test:test_support",
+    "//chrome/chrome_cleaner/interfaces:parser_interface",
+    "//chrome/chrome_cleaner/ipc:mojo_task_runner",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/parsers/json_parser:json_parser",
+    "//chrome/chrome_cleaner/parsers/json_parser:json_splicer",
+    "//mojo/public/cpp/bindings:bindings",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/chrome_cleaner/json_parser/json_parser_impl.cc b/chrome/chrome_cleaner/parsers/target/parser_impl.cc
similarity index 73%
rename from chrome/chrome_cleaner/json_parser/json_parser_impl.cc
rename to chrome/chrome_cleaner/parsers/target/parser_impl.cc
index b2243e3..210c9a1 100644
--- a/chrome/chrome_cleaner/json_parser/json_parser_impl.cc
+++ b/chrome/chrome_cleaner/parsers/target/parser_impl.cc
@@ -2,22 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/chrome_cleaner/json_parser/json_parser_impl.h"
+#include "chrome/chrome_cleaner/parsers/target/parser_impl.h"
 
 #include "base/json/json_reader.h"
 #include "base/values.h"
 
 namespace chrome_cleaner {
 
-JsonParserImpl::JsonParserImpl(mojom::JsonParserRequest request,
-                               base::OnceClosure connection_error_handler)
+ParserImpl::ParserImpl(mojom::ParserRequest request,
+                       base::OnceClosure connection_error_handler)
     : binding_(this, std::move(request)) {
   binding_.set_connection_error_handler(std::move(connection_error_handler));
 }
 
-JsonParserImpl::~JsonParserImpl() = default;
+ParserImpl::~ParserImpl() = default;
 
-void JsonParserImpl::Parse(const std::string& json, ParseCallback callback) {
+void ParserImpl::ParseJson(const std::string& json,
+                           ParseJsonCallback callback) {
   int error_code;
   std::string error;
   std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError(
diff --git a/chrome/chrome_cleaner/parsers/target/parser_impl.h b/chrome/chrome_cleaner/parsers/target/parser_impl.h
new file mode 100644
index 0000000..1e1891a
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/target/parser_impl.h
@@ -0,0 +1,29 @@
+// 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_CHROME_CLEANER_PARSERS_TARGET_PARSER_IMPL_H_
+#define CHROME_CHROME_CLEANER_PARSERS_TARGET_PARSER_IMPL_H_
+
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace chrome_cleaner {
+
+class ParserImpl : public mojom::Parser {
+ public:
+  explicit ParserImpl(mojom::ParserRequest request,
+                      base::OnceClosure connection_error_handler);
+  ~ParserImpl() override;
+
+  // mojom::Parser
+  void ParseJson(const std::string& json,
+                 ParserImpl::ParseJsonCallback callback) override;
+
+ private:
+  mojo::Binding<mojom::Parser> binding_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_TARGET_PARSER_IMPL_H_
diff --git a/chrome/chrome_cleaner/json_parser/json_parser_impl_unittest.cc b/chrome/chrome_cleaner/parsers/target/parser_impl_unittest.cc
similarity index 67%
rename from chrome/chrome_cleaner/json_parser/json_parser_impl_unittest.cc
rename to chrome/chrome_cleaner/parsers/target/parser_impl_unittest.cc
index 597b3ea..685033bb 100644
--- a/chrome/chrome_cleaner/json_parser/json_parser_impl_unittest.cc
+++ b/chrome/chrome_cleaner/parsers/target/parser_impl_unittest.cc
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/chrome_cleaner/json_parser/json_parser_impl.h"
+#include "chrome/chrome_cleaner/parsers/target/parser_impl.h"
 
 #include "base/bind.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/test/test_timeouts.h"
 #include "base/values.h"
-#include "chrome/chrome_cleaner/interfaces/json_parser.mojom.h"
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
 #include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
-#include "chrome/chrome_cleaner/json_parser/sandboxed_json_parser.h"
+#include "chrome/chrome_cleaner/parsers/json_parser/sandboxed_json_parser.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -25,14 +25,14 @@
 const char kTestJsonText[] = "{ \"name\": \"Jason\" }";
 const char kInvalidJsonText[] = "{ name: jason }";
 
-class JsonParserImplTest : public testing::Test {
+class ParserImplTest : public testing::Test {
  public:
-  JsonParserImplTest()
+  ParserImplTest()
       : task_runner_(MojoTaskRunner::Create()),
-        json_parser_ptr_(new mojom::JsonParserPtr(),
-                         base::OnTaskRunnerDeleter(task_runner_)),
-        json_parser_impl_(nullptr, base::OnTaskRunnerDeleter(task_runner_)),
-        sandboxed_json_parser_(task_runner_.get(), json_parser_ptr_.get()) {
+        parser_ptr_(new mojom::ParserPtr(),
+                    base::OnTaskRunnerDeleter(task_runner_)),
+        parser_impl_(nullptr, base::OnTaskRunnerDeleter(task_runner_)),
+        sandboxed_json_parser_(task_runner_.get(), parser_ptr_.get()) {
     BindParser();
   }
 
@@ -41,25 +41,24 @@
     task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
-            [](mojom::JsonParserPtr* json_parser,
-               std::unique_ptr<JsonParserImpl, base::OnTaskRunnerDeleter>*
-                   json_parser_impl) {
-              json_parser_impl->reset(new JsonParserImpl(
-                  mojo::MakeRequest(json_parser), base::DoNothing()));
+            [](mojom::ParserPtr* parser,
+               std::unique_ptr<ParserImpl, base::OnTaskRunnerDeleter>*
+                   parser_impl) {
+              parser_impl->reset(
+                  new ParserImpl(mojo::MakeRequest(parser), base::DoNothing()));
             },
-            json_parser_ptr_.get(), &json_parser_impl_));
+            parser_ptr_.get(), &parser_impl_));
   }
 
   scoped_refptr<MojoTaskRunner> task_runner_;
-  std::unique_ptr<mojom::JsonParserPtr, base::OnTaskRunnerDeleter>
-      json_parser_ptr_;
-  std::unique_ptr<JsonParserImpl, base::OnTaskRunnerDeleter> json_parser_impl_;
+  std::unique_ptr<mojom::ParserPtr, base::OnTaskRunnerDeleter> parser_ptr_;
+  std::unique_ptr<ParserImpl, base::OnTaskRunnerDeleter> parser_impl_;
   SandboxedJsonParser sandboxed_json_parser_;
 };
 
 }  // namespace
 
-TEST_F(JsonParserImplTest, ParseJson) {
+TEST_F(ParserImplTest, ParseJson) {
   WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
                      WaitableEvent::InitialState::NOT_SIGNALED);
   sandboxed_json_parser_.Parse(
@@ -82,7 +81,7 @@
   EXPECT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
 }
 
-TEST_F(JsonParserImplTest, ParseJsonError) {
+TEST_F(ParserImplTest, ParseJsonError) {
   WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
                      WaitableEvent::InitialState::NOT_SIGNALED);
   sandboxed_json_parser_.Parse(
diff --git a/chrome/chrome_cleaner/parsers/target/sandbox_setup.cc b/chrome/chrome_cleaner/parsers/target/sandbox_setup.cc
new file mode 100644
index 0000000..e5d0c2bb8d
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/target/sandbox_setup.cc
@@ -0,0 +1,74 @@
+// 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/chrome_cleaner/parsers/target/sandbox_setup.h"
+
+#include <utility>
+
+#include "chrome/chrome_cleaner/interfaces/parser_interface.mojom.h"
+#include "chrome/chrome_cleaner/ipc/mojo_sandbox_hooks.h"
+#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
+#include "chrome/chrome_cleaner/os/early_exit.h"
+#include "chrome/chrome_cleaner/parsers/target/parser_impl.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+class ParserSandboxTargetHooks : public MojoSandboxTargetHooks {
+ public:
+  explicit ParserSandboxTargetHooks(MojoTaskRunner* mojo_task_runner)
+      : mojo_task_runner_(mojo_task_runner) {}
+
+  ~ParserSandboxTargetHooks() override {
+    // Delete the mojo objects on the IPC thread.
+    mojo_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(
+                       [](std::unique_ptr<ParserImpl> parser_impl) {
+                         parser_impl.reset();
+                       },
+                       base::Passed(&parser_impl_)));
+  }
+
+  // SandboxTargetHooks
+  ResultCode TargetDroppedPrivileges(
+      const base::CommandLine& command_line) override {
+    mojom::ParserRequest request(ExtractSandboxMessagePipe(command_line));
+
+    // This loop will run forever. Once the communication channel with the
+    // broker process is broken, mojo error handler will abort this process.
+    base::RunLoop run_loop;
+    mojo_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ParserSandboxTargetHooks::CreateParserImpl,
+                       base::Unretained(this), base::Passed(&request)));
+    run_loop.Run();
+    return RESULT_CODE_SUCCESS;
+  }
+
+ private:
+  void CreateParserImpl(mojom::ParserRequest request) {
+    parser_impl_ = std::make_unique<ParserImpl>(std::move(request),
+                                                base::BindOnce(&EarlyExit, 1));
+  }
+
+  MojoTaskRunner* mojo_task_runner_;
+  base::MessageLoop message_loop_;
+  std::unique_ptr<ParserImpl> parser_impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(ParserSandboxTargetHooks);
+};
+
+}  // namespace
+
+ResultCode RunParserSandboxTarget(const base::CommandLine& command_line,
+                                  sandbox::TargetServices* target_services) {
+  scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
+  ParserSandboxTargetHooks target_hooks(mojo_task_runner.get());
+
+  return RunSandboxTarget(command_line, target_services, &target_hooks);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/parsers/target/sandbox_setup.h b/chrome/chrome_cleaner/parsers/target/sandbox_setup.h
new file mode 100644
index 0000000..ce2d724
--- /dev/null
+++ b/chrome/chrome_cleaner/parsers/target/sandbox_setup.h
@@ -0,0 +1,19 @@
+// 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_CHROME_CLEANER_PARSERS_TARGET_SANDBOX_SETUP_H_
+#define CHROME_CHROME_CLEANER_PARSERS_TARGET_SANDBOX_SETUP_H_
+
+#include "base/command_line.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "sandbox/win/src/sandbox.h"
+
+namespace chrome_cleaner {
+
+ResultCode RunParserSandboxTarget(const base::CommandLine& command_line,
+                                  sandbox::TargetServices* target_services);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_PARSERS_TARGET_SANDBOX_SETUP_H_
diff --git a/chrome/chrome_cleaner/settings/settings_types.h b/chrome/chrome_cleaner/settings/settings_types.h
index 6789d79..17d5d43 100644
--- a/chrome/chrome_cleaner/settings/settings_types.h
+++ b/chrome/chrome_cleaner/settings/settings_types.h
@@ -14,7 +14,7 @@
   kNonSandboxed = 0,
   kTest,
   kEset,
-  kJsonParser,
+  kParser,
   kZipArchiver,
   kNumValues,
 };
diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc
index e869d40e0..6cc2c913 100644
--- a/chrome/common/chrome_paths.cc
+++ b/chrome/common/chrome_paths.cc
@@ -5,9 +5,9 @@
 #include "chrome/common/chrome_paths.h"
 
 #include "base/files/file_util.h"
-#include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/native_library.h"
+#include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
@@ -77,8 +77,10 @@
     FILE_PATH_LITERAL("/run/tpm_firmware_update_srk_vulnerable_roca");
 #endif  // defined(OS_CHROMEOS)
 
-static base::LazyInstance<base::FilePath>::DestructorAtExit
-    g_invalid_specified_user_data_dir = LAZY_INSTANCE_INITIALIZER;
+base::FilePath& GetInvalidSpecifiedUserDataDirInternal() {
+  static base::NoDestructor<base::FilePath> s;
+  return *s;
+}
 
 // Gets the path for internal plugins.
 bool GetInternalPluginsDirectory(base::FilePath* result) {
@@ -606,11 +608,11 @@
 }
 
 void SetInvalidSpecifiedUserDataDir(const base::FilePath& user_data_dir) {
-  g_invalid_specified_user_data_dir.Get() = user_data_dir;
+  GetInvalidSpecifiedUserDataDirInternal() = user_data_dir;
 }
 
 const base::FilePath& GetInvalidSpecifiedUserDataDir() {
-  return g_invalid_specified_user_data_dir.Get();
+  return GetInvalidSpecifiedUserDataDirInternal();
 }
 
 }  // namespace chrome
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index a7d8e6a..a4f836f 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -217,5 +217,9 @@
     // daemon startup, and 2. the initial D-Bus -> Mojo IPC bootstrap.
     // |callback|: Called when the operation has completed.
     static void bootstrapMachineLearningService(VoidCallback callback);
+
+    // Enable/disable the Google Assistant
+    // |callback|: Called when the operation has completed.
+    static void setAssistantEnabled(boolean enabled, long timeout_ms, VoidCallback callback);
   };
 };
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index bd1218ae..6fb885ec1 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1176,13 +1176,12 @@
     content::RenderFrame* render_frame,
     const WebURLRequest& failed_request,
     const blink::WebURLError& web_error,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   PrepareErrorPageInternal(
       render_frame, failed_request,
       error_page::Error::NetError(web_error.url(), web_error.reason(),
                                   web_error.has_copy_in_cache()),
-      error_html, error_description);
+      error_html);
 }
 
 void ChromeContentRendererClient::PrepareErrorPageForHttpStatusError(
@@ -1190,12 +1189,10 @@
     const WebURLRequest& failed_request,
     const GURL& unreachable_url,
     int http_status,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   PrepareErrorPageInternal(
       render_frame, failed_request,
-      error_page::Error::HttpError(unreachable_url, http_status), error_html,
-      error_description);
+      error_page::Error::HttpError(unreachable_url, http_status), error_html);
 }
 
 void ChromeContentRendererClient::GetErrorDescription(
@@ -1213,15 +1210,12 @@
     content::RenderFrame* render_frame,
     const WebURLRequest& failed_request,
     const error_page::Error& error,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   bool is_post = failed_request.HttpMethod().Ascii() == "POST";
   bool is_ignoring_cache =
       failed_request.GetCacheMode() == FetchCacheMode::kBypassCache;
   NetErrorHelper::Get(render_frame)
       ->PrepareErrorPage(error, is_post, is_ignoring_cache, error_html);
-  if (error_description)
-    GetErrorDescriptionInternal(failed_request, error, error_description);
 }
 
 void ChromeContentRendererClient::GetErrorDescriptionInternal(
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index 2c440b2..cf175024 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -132,15 +132,13 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLRequest& failed_request,
                         const blink::WebURLError& error,
-                        std::string* error_html,
-                        base::string16* error_description) override;
+                        std::string* error_html) override;
   void PrepareErrorPageForHttpStatusError(
       content::RenderFrame* render_frame,
       const blink::WebURLRequest& failed_request,
       const GURL& unreachable_url,
       int http_status,
-      std::string* error_html,
-      base::string16* error_description) override;
+      std::string* error_html) override;
 
   void GetErrorDescription(const blink::WebURLRequest& failed_request,
                            const blink::WebURLError& error,
@@ -284,8 +282,7 @@
   void PrepareErrorPageInternal(content::RenderFrame* render_frame,
                                 const blink::WebURLRequest& failed_request,
                                 const error_page::Error& error,
-                                std::string* error_html,
-                                base::string16* error_description);
+                                std::string* error_html);
 
   void GetErrorDescriptionInternal(const blink::WebURLRequest& failed_request,
                                    const error_page::Error& error,
diff --git a/chrome/renderer/pepper/pepper_flash_renderer_host.cc b/chrome/renderer/pepper/pepper_flash_renderer_host.cc
index 63a3395..42248ea 100644
--- a/chrome/renderer/pepper/pepper_flash_renderer_host.cc
+++ b/chrome/renderer/pepper/pepper_flash_renderer_host.cc
@@ -9,9 +9,9 @@
 #include <map>
 #include <vector>
 
-#include "base/lazy_instance.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_util.h"
 #include "components/pdf/renderer/pepper_pdf_host.h"
 #include "content/public/renderer/pepper_plugin_instance.h"
@@ -101,8 +101,10 @@
   FLASH_NAVIGATE_USAGE_ENUM_COUNT
 };
 
-static base::LazyInstance<std::map<std::string, FlashNavigateUsage>>::
-    DestructorAtExit g_rejected_headers = LAZY_INSTANCE_INITIALIZER;
+std::map<std::string, FlashNavigateUsage>& GetRejectedHeaders() {
+  static base::NoDestructor<std::map<std::string, FlashNavigateUsage>> s;
+  return *s;
+}
 
 bool IsSimpleHeader(const std::string& lower_case_header_name,
                     const std::string& header_value) {
@@ -303,7 +305,7 @@
     return PP_ERROR_FAILED;
 
   std::map<std::string, FlashNavigateUsage>& rejected_headers =
-      g_rejected_headers.Get();
+      GetRejectedHeaders();
   if (rejected_headers.empty()) {
     for (size_t i = 0; i < arraysize(kRejectedHttpRequestHeaders); ++i)
       rejected_headers[kRejectedHttpRequestHeaders[i]] =
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
index 9f1497c..62ec6602 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
@@ -12,6 +12,7 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
 #include "chrome/renderer/safe_browsing/feature_extractor_clock.h"
 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
 #include "chrome/renderer/safe_browsing/scorer.h"
@@ -30,19 +31,24 @@
 
 namespace safe_browsing {
 
-static GURL StripRef(const GURL& url) {
+namespace {
+
+GURL StripRef(const GURL& url) {
   GURL::Replacements replacements;
   replacements.ClearRef();
   return url.ReplaceComponents(replacements);
 }
 
-typedef std::set<PhishingClassifierDelegate*> PhishingClassifierDelegates;
-static base::LazyInstance<PhishingClassifierDelegates>::DestructorAtExit
-    g_delegates = LAZY_INSTANCE_INITIALIZER;
+std::set<PhishingClassifierDelegate*> PhishingClassifierDelegates() {
+  static base::NoDestructor<std::set<PhishingClassifierDelegate*>> s;
+  return *s;
+}
 
-static base::LazyInstance<std::unique_ptr<const safe_browsing::Scorer>>::
+base::LazyInstance<std::unique_ptr<const safe_browsing::Scorer>>::
     DestructorAtExit g_phishing_scorer = LAZY_INSTANCE_INITIALIZER;
 
+}  // namespace
+
 // static
 void PhishingClassifierFilter::Create(
     mojom::PhishingModelSetterRequest request) {
@@ -65,10 +71,8 @@
       return;
     }
   }
-  PhishingClassifierDelegates::iterator i;
-  for (i = g_delegates.Get().begin(); i != g_delegates.Get().end(); ++i) {
-    (*i)->SetPhishingScorer(scorer);
-  }
+  for (auto* delegate : PhishingClassifierDelegates())
+    delegate->SetPhishingScorer(scorer);
   g_phishing_scorer.Get().reset(scorer);
 }
 
@@ -88,7 +92,7 @@
       last_main_frame_transition_(ui::PAGE_TRANSITION_LINK),
       have_page_text_(false),
       is_classifying_(false) {
-  g_delegates.Get().insert(this);
+  PhishingClassifierDelegates().insert(this);
   if (!classifier) {
     classifier =
         new PhishingClassifier(render_frame, new FeatureExtractorClock());
@@ -106,7 +110,7 @@
 
 PhishingClassifierDelegate::~PhishingClassifierDelegate() {
   CancelPendingClassification(SHUTDOWN);
-  g_delegates.Get().erase(this);
+  PhishingClassifierDelegates().erase(this);
 }
 
 void PhishingClassifierDelegate::SetPhishingScorer(
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fc33066..c80ee09 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1593,7 +1593,6 @@
         sources += [
           "../browser/ui/views/frame/opaque_browser_frame_view_browsertest.cc",
           "../browser/ui/views/policy/enterprise_startup_dialog_view_browsertest.cc",
-          "../browser/ui/views/profiles/forced_reauthentication_dialog_view_browsertest.cc",
           "../browser/ui/views/toolbar/outdated_upgrade_bubble_view_browsertest.cc",
         ]
       }
@@ -2969,6 +2968,7 @@
 
   if (enable_offline_pages) {
     sources += [
+      "../browser/offline_pages/auto_fetch_page_load_watcher_unittest.cc",
       "../browser/offline_pages/background_loader_offliner_unittest.cc",
       "../browser/offline_pages/download_archive_manager_unittest.cc",
       "../browser/offline_pages/offline_page_auto_fetcher_service_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/autotest_private/test.js b/chrome/test/data/extensions/api_test/autotest_private/test.js
index 7e3e2e92e..2119e4e 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -206,5 +206,10 @@
     chrome.autotestPrivate.getPrinterList(function(){
       chrome.test.succeed();
     });
-  }
+  },
+  function setAssistantEnabled() {
+    chrome.autotestPrivate.setAssistantEnabled(true, 1000 /* timeout_ms */,
+        chrome.test.callbackFail(
+            'Assistant is not available for the current user'));
+  },
 ]);
diff --git a/chrome/test/data/extensions/updater/ping_reply_1.json b/chrome/test/data/extensions/updater/ping_reply_1.json
new file mode 100644
index 0000000..41bcae4
--- /dev/null
+++ b/chrome/test/data/extensions/updater/ping_reply_1.json
@@ -0,0 +1,11 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":4055, "elapsed_seconds":44620},
+ "app":[
+  {"appid":"aohghmighlieiainnegkcijnfilokake",
+   "status":"ok",
+   "event":[{"status":"ok"},{"status":"ok"}]
+  }
+ ]
+}}
diff --git a/chrome/test/data/extensions/updater/updatecheck_reply_noupdate_1.json b/chrome/test/data/extensions/updater/updatecheck_reply_noupdate_1.json
new file mode 100644
index 0000000..94d1f89
--- /dev/null
+++ b/chrome/test/data/extensions/updater/updatecheck_reply_noupdate_1.json
@@ -0,0 +1,10 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":4055, "elapsed_seconds":44620},
+ "app":[
+  {"appid":"aohghmighlieiainnegkcijnfilokake",
+    "updatecheck":{"status":"noupdate"}
+  }
+ ]
+}}
diff --git a/chrome/test/data/extensions/updater/updatecheck_reply_update_1.json b/chrome/test/data/extensions/updater/updatecheck_reply_update_1.json
new file mode 100644
index 0000000..3e2b15cd
--- /dev/null
+++ b/chrome/test/data/extensions/updater/updatecheck_reply_update_1.json
@@ -0,0 +1,27 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "server":"prod",
+ "daystart":{"elapsed_days":4054, "elapsed_seconds":61236},
+ "app":[
+  {"appid":"aohghmighlieiainnegkcijnfilokake",
+   "cohort":"",
+   "cohortname":"",
+   "status":"ok",
+   "updatecheck":{
+   "status":"ok",
+   "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+   "actions":{"action":[{"run":"this"}]},
+   "manifest":{
+    "version":"0.1",
+    "packages":{"package":[{
+      "fp":"1.d6f11c606729d553e9c9b3d0db9e5d51567ea969bedd98008cce7b9415a17490",
+      "hash_sha256":"d6f11c606729d553e9c9b3d0db9e5d51567ea969bedd98008cce7b9415a17490",
+      "name":"v1.crx",
+      "required":"true",
+      "size":23276
+    }]}}
+    }
+   }
+ ]
+}}
diff --git a/chrome/test/data/navigation_predictor/long_page_with_anchors-1.html b/chrome/test/data/navigation_predictor/long_page_with_anchors-1.html
deleted file mode 100644
index 83a4c9cc..0000000
--- a/chrome/test/data/navigation_predictor/long_page_with_anchors-1.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
-  <head>
-  </head>
-  <body>
-    <a id="google" href="https://google.com">Google</a>
-    <img src="large_image_takes_all_viewport.gif" height="4200" width="4200">
-    <a id="example2" href="https://example2.com">Below viewport</a>
-    <a id="example3" href="long_page_with_anchors-2.html">Below viewport differ by one</a>
-  </body>
-</html>
\ No newline at end of file
diff --git a/chrome_elf/BUILD.gn b/chrome_elf/BUILD.gn
index d0949dc..7f780b5 100644
--- a/chrome_elf/BUILD.gn
+++ b/chrome_elf/BUILD.gn
@@ -63,6 +63,8 @@
   ]
   if (target_cpu == "x86") {
     sources += [ "chrome_elf_x86.def" ]
+  } else if (target_cpu == "arm64") {
+    sources += [ "chrome_elf_arm64.def" ]
   } else {
     sources += [ "chrome_elf_x64.def" ]
   }
diff --git a/chrome_elf/chrome_elf_arm64.def b/chrome_elf/chrome_elf_arm64.def
new file mode 100644
index 0000000..dc0fc69e
--- /dev/null
+++ b/chrome_elf/chrome_elf_arm64.def
@@ -0,0 +1,38 @@
+; Copyright 2013 The Chromium Authors. All rights reserved.
+; Use of this source code is governed by a BSD-style license that can be
+; found in the LICENSE file.
+LIBRARY  "chrome_elf.dll"
+
+EXPORTS
+  ; When functions are added to this file, they must also be added to
+  ; chrome_elf_x86.def and chrome_elf_x64.def
+
+  ; From components/crash/content/app/crash_export_stubs.cc
+  CrashForException_ExportThunk
+  DumpHungProcessWithPtype_ExportThunk
+  GetCrashpadDatabasePath_ExportThunk
+  GetCrashReports_ExportThunk
+  InjectDumpForHungInput_ExportThunk
+  RequestSingleCrashUpload_ExportThunk
+  SetUploadConsent_ExportThunk
+
+  ; From chrome_elf/crash/crash_helper.cc
+  SetMetricsClientId
+
+  ; From chrome_elf/chrome_elf_main.cc
+  DumpProcessWithoutCrash
+  GetUserDataDirectoryThunk
+  SignalChromeElf
+  SignalInitializeCrashReporting
+
+  ; From chrome/install_static
+  GetInstallDetailsPayload
+
+  ; From chrome_elf/blacklist/blacklist.cc
+  AddDllToBlacklist
+  IsBlacklistInitialized
+  SuccessfullyBlocked
+
+  ; From chrome_elf/third_party_dlls/logs
+  DrainLog
+  RegisterLogNotification
diff --git a/chrome_elf/chrome_elf_x64.def b/chrome_elf/chrome_elf_x64.def
index f4b3dfb..a6ccf44 100644
--- a/chrome_elf/chrome_elf_x64.def
+++ b/chrome_elf/chrome_elf_x64.def
@@ -5,7 +5,7 @@
 
 EXPORTS
   ; When functions are added to this file, they must also be added to
-  ; chrome_elf_x86.def
+  ; chrome_elf_x86.def and chrome_elf_arm64.def
 
   ; From components/crash/content/app/crash_export_stubs.cc
   CrashForException_ExportThunk
diff --git a/chrome_elf/chrome_elf_x86.def b/chrome_elf/chrome_elf_x86.def
index 258af5b..a695720 100644
--- a/chrome_elf/chrome_elf_x86.def
+++ b/chrome_elf/chrome_elf_x86.def
@@ -5,7 +5,7 @@
 
 EXPORTS
   ; When functions are added to this file, they must also be added to
-  ; chrome_elf_x64.def
+  ; chrome_elf_x64.def and chrome_elf_arm64.def
 
   ; From components/crash/content/app/crash_export_stubs.cc
   CrashForException_ExportThunk
diff --git a/chromeos/components/proximity_auth/metrics.cc b/chromeos/components/proximity_auth/metrics.cc
index c15e71d..4ec3b2d 100644
--- a/chromeos/components/proximity_auth/metrics.cc
+++ b/chromeos/components/proximity_auth/metrics.cc
@@ -56,20 +56,6 @@
                            rolling_rssi);
 }
 
-void RecordAuthProximityTransmitPowerDelta(int transmit_power_delta) {
-  if (transmit_power_delta != kUnknownProximityValue)
-    transmit_power_delta = std::min(50, std::max(-100, transmit_power_delta));
-
-  base::UmaHistogramSparse("EasyUnlock.AuthProximity.TransmitPowerDelta",
-                           transmit_power_delta);
-}
-
-void RecordAuthProximityTimeSinceLastZeroRssi(
-    base::TimeDelta time_since_last_zero_rssi) {
-  UMA_HISTOGRAM_TIMES("EasyUnlock.AuthProximity.TimeSinceLastZeroRssi",
-                      time_since_last_zero_rssi);
-}
-
 void RecordAuthProximityRemoteDeviceModelHash(const std::string& device_model) {
   base::UmaHistogramSparse("EasyUnlock.AuthProximity.RemoteDeviceModelHash",
                            HashDeviceModelName(device_model));
diff --git a/chromeos/components/proximity_auth/metrics.h b/chromeos/components/proximity_auth/metrics.h
index 2de3043..7f7c732 100644
--- a/chromeos/components/proximity_auth/metrics.h
+++ b/chromeos/components/proximity_auth/metrics.h
@@ -31,16 +31,6 @@
 // are available.
 void RecordAuthProximityRollingRssi(int rolling_rssi);
 
-// Records the difference between the transmit power and maximum transmit power,
-// upon a successful auth attempt. |transmit_power_delta| should be set to
-// |kUnknownProximityValue| if no Tx power readings are available.
-void RecordAuthProximityTransmitPowerDelta(int transmit_power_delta);
-
-// Records the time elapsed since the last zero RSSI value was read, upon a
-// successful auth attempt.
-void RecordAuthProximityTimeSinceLastZeroRssi(
-    base::TimeDelta time_since_last_zero_rssi);
-
 // Records the phone model used for a successful auth attempt. The model is
 // recorded as a 32-bit hash due to the limits of UMA. |device_model| should be
 // set to |kUnknownDeviceModel| if the device model could not be read.
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 1b5afb9..5f1b378 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -107,7 +107,6 @@
     network::NetworkConnectionTracker* network_connection_tracker)
     : action_module_(std::make_unique<action::CrosActionModule>(this)),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
-      chromium_api_delegate_(),
       assistant_settings_manager_(
           std::make_unique<AssistantSettingsManagerImpl>(this)),
       display_connection_(std::make_unique<CrosDisplayConnection>(this)),
@@ -787,8 +786,6 @@
   assistant_manager_internal->SetDisplayConnection(display_connection_.get());
   assistant_manager_internal->RegisterActionModule(action_module_.get());
   assistant_manager_internal->SetAssistantManagerDelegate(this);
-  assistant_manager_internal->GetFuchsiaApiHelperOrDie()->SetFuchsiaApiDelegate(
-      &chromium_api_delegate_);
   assistant_manager->AddConversationStateListener(this);
   assistant_manager->AddDeviceStateListener(this);
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index d2ede69..420fbb1 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -19,7 +19,6 @@
 #include "chromeos/assistant/internal/internal_util.h"
 #include "chromeos/services/assistant/assistant_manager_service.h"
 #include "chromeos/services/assistant/assistant_settings_manager_impl.h"
-#include "chromeos/services/assistant/chromium_api_delegate.h"
 #include "chromeos/services/assistant/platform_api_impl.h"
 #include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
 #include "libassistant/shared/internal_api/assistant_manager_delegate.h"
@@ -213,7 +212,6 @@
   std::unique_ptr<PlatformApiImpl> platform_api_;
   std::unique_ptr<action::CrosActionModule> action_module_;
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
-  ChromiumApiDelegate chromium_api_delegate_;
   std::unique_ptr<assistant_client::AssistantManager> assistant_manager_;
   std::unique_ptr<AssistantSettingsManagerImpl> assistant_settings_manager_;
   // same ownership as assistant_manager_.
diff --git a/components/autofill_assistant_strings.grdp b/components/autofill_assistant_strings.grdp
index 01f2feb..1a0e36e 100644
--- a/components/autofill_assistant_strings.grdp
+++ b/components/autofill_assistant_strings.grdp
@@ -17,7 +17,7 @@
       Confirm
     </message>
     <message name="IDS_AUTOFILL_ASSISTANT_TERMS" desc="The text for the terms and service acceptance checkbox. Sentence-cased." formatter_data="android_java">
-      I know the terms and conditions and accept them (leave unclicked to read the terms and conditions from the website).
+      I know the terms and conditions, privacy policy and right of withdrawal of <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="ORIGIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph> (leave unchecked to read).
     </message>
   </if>
 </grit-part>
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index 2ea8f1a..3b00214 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -470,7 +470,6 @@
   script = "//components/cronet/tools/generate_proguard_file.py"
   sources = [
     "//base/android/proguard/chromium_code.flags",
-    "//base/android/proguard/explicit_jni_registration.flags",
     "//components/cronet/android/cronet_impl_native_proguard.cfg",
   ]
   outputs = [
diff --git a/components/domain_reliability/BUILD.gn b/components/domain_reliability/BUILD.gn
index 71f0b74..7feac4a 100644
--- a/components/domain_reliability/BUILD.gn
+++ b/components/domain_reliability/BUILD.gn
@@ -86,9 +86,6 @@
   deps = [
     ":bake_in_configs",
     "//base",
-    "//components/keyed_service/core",
-    "//content/public/browser",
-    "//content/public/common",
     "//net",
     "//url",
   ]
@@ -117,8 +114,6 @@
     ":domain_reliability",
     "//base",
     "//base/test:test_support",
-    "//content/public/browser",
-    "//content/test:test_support",
     "//net:test_support",
     "//testing/gtest",
   ]
diff --git a/components/domain_reliability/DEPS b/components/domain_reliability/DEPS
index bb465a77..54e5ce0 100644
--- a/components/domain_reliability/DEPS
+++ b/components/domain_reliability/DEPS
@@ -4,9 +4,5 @@
 
 include_rules = [
   "+net",
-  "+components/keyed_service/core",
-  "+content/public/browser",
-  "+content/public/test",
-  "+third_party/blink/public",
 ]
 
diff --git a/components/domain_reliability/context.cc b/components/domain_reliability/context.cc
index b7088e15..7c6217e8 100644
--- a/components/domain_reliability/context.cc
+++ b/components/domain_reliability/context.cc
@@ -231,8 +231,8 @@
 void DomainReliabilityContext::RemoveOldestBeacon() {
   DCHECK(!beacons_.empty());
 
-  VLOG(1) << "Beacon queue for " << config().origin << " full; "
-          << "removing oldest beacon";
+  DVLOG(1) << "Beacon queue for " << config().origin << " full; "
+           << "removing oldest beacon";
 
   beacons_.pop_front();
 
diff --git a/components/domain_reliability/context_manager.cc b/components/domain_reliability/context_manager.cc
index 2290eb4..b1084a3e 100644
--- a/components/domain_reliability/context_manager.cc
+++ b/components/domain_reliability/context_manager.cc
@@ -36,8 +36,8 @@
   std::string key = origin.host();
 
   if (!contexts_.count(key) && !removed_contexts_.count(key)) {
-    LOG(WARNING) << "Ignoring NEL header for unknown origin " << origin.spec()
-                 << ".";
+    DLOG(WARNING) << "Ignoring NEL header for unknown origin " << origin.spec()
+                  << ".";
     return;
   }
 
diff --git a/components/domain_reliability/header.cc b/components/domain_reliability/header.cc
index 092f90f7..81f19084 100644
--- a/components/domain_reliability/header.cc
+++ b/components/domain_reliability/header.cc
@@ -234,7 +234,7 @@
       include_subdomains = true;
       got_include_subdomains = true;
     } else {
-      LOG(WARNING) << "Ignoring unknown NEL header directive " << name << ".";
+      DLOG(WARNING) << "Ignoring unknown NEL header directive " << name << ".";
     }
   }
 
diff --git a/components/domain_reliability/monitor.cc b/components/domain_reliability/monitor.cc
index 4a7f2f8..14b2bcf 100644
--- a/components/domain_reliability/monitor.cc
+++ b/components/domain_reliability/monitor.cc
@@ -78,7 +78,7 @@
     const std::string& upload_reporter_string,
     const DomainReliabilityContext::UploadAllowedCallback&
         upload_allowed_callback,
-    const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
+    const scoped_refptr<base::SingleThreadTaskRunner>& main_thread,
     const scoped_refptr<base::SingleThreadTaskRunner>& network_thread)
     : time_(new ActualTime()),
       upload_reporter_string_(upload_reporter_string),
@@ -87,19 +87,19 @@
           DomainReliabilityScheduler::Params::GetFromFieldTrialsOrDefaults()),
       dispatcher_(time_.get()),
       context_manager_(this),
-      pref_task_runner_(pref_thread),
+      main_task_runner_(main_thread),
       network_task_runner_(network_thread),
       moved_to_network_thread_(false),
       discard_uploads_set_(false),
       weak_factory_(this) {
-  DCHECK(OnPrefThread());
+  DCHECK(OnMainThread());
 }
 
 DomainReliabilityMonitor::DomainReliabilityMonitor(
     const std::string& upload_reporter_string,
     const DomainReliabilityContext::UploadAllowedCallback&
         upload_allowed_callback,
-    const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
+    const scoped_refptr<base::SingleThreadTaskRunner>& main_thread,
     const scoped_refptr<base::SingleThreadTaskRunner>& network_thread,
     std::unique_ptr<MockableTime> time)
     : time_(std::move(time)),
@@ -109,12 +109,12 @@
           DomainReliabilityScheduler::Params::GetFromFieldTrialsOrDefaults()),
       dispatcher_(time_.get()),
       context_manager_(this),
-      pref_task_runner_(pref_thread),
+      main_task_runner_(main_thread),
       network_task_runner_(network_thread),
       moved_to_network_thread_(false),
       discard_uploads_set_(false),
       weak_factory_(this) {
-  DCHECK(OnPrefThread());
+  DCHECK(OnMainThread());
 }
 
 DomainReliabilityMonitor::~DomainReliabilityMonitor() {
@@ -122,12 +122,12 @@
     net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
     DCHECK(OnNetworkThread());
   } else {
-    DCHECK(OnPrefThread());
+    DCHECK(OnMainThread());
   }
 }
 
-void DomainReliabilityMonitor::MoveToNetworkThread() {
-  DCHECK(OnPrefThread());
+void DomainReliabilityMonitor::InitializeOnNetworkThread() {
+  DCHECK(OnMainThread());
   DCHECK(!moved_to_network_thread_);
 
   network_task_runner_->PostTask(
@@ -410,9 +410,9 @@
   std::string ignored_header_value;
   if (request.response_info.headers->EnumerateHeader(
           &iter, kHeaderNameString, &ignored_header_value)) {
-    LOG(WARNING) << "Request to " << request.url << " had (at least) two "
-                 << kHeaderNameString << " headers: \"" << header_value
-                 << "\" and \"" << ignored_header_value << "\".";
+    DLOG(WARNING) << "Request to " << request.url << " had (at least) two "
+                  << kHeaderNameString << " headers: \"" << header_value
+                  << "\" and \"" << ignored_header_value << "\".";
     return;
   }
 
@@ -430,9 +430,9 @@
       context_manager_.ClearConfig(origin);
       break;
     case DomainReliabilityHeader::PARSE_ERROR:
-      LOG(WARNING) << "Request to " << request.url << " had invalid "
-                   << kHeaderNameString << " header \"" << header_value
-                   << "\".";
+      DLOG(WARNING) << "Request to " << request.url << " had invalid "
+                    << kHeaderNameString << " header \"" << header_value
+                    << "\".";
       break;
   }
 }
diff --git a/components/domain_reliability/monitor.h b/components/domain_reliability/monitor.h
index 512e438..b4cd430 100644
--- a/components/domain_reliability/monitor.h
+++ b/components/domain_reliability/monitor.h
@@ -51,14 +51,14 @@
     : public net::NetworkChangeNotifier::NetworkChangeObserver,
       DomainReliabilityContext::Factory {
  public:
-  // Creates a Monitor. |local_state_pref_service| must live on |pref_thread|
-  // (which should be the current thread); |network_thread| is the thread
+  // Creates a Monitor. |main_thread| is the current thread, which may or may
+  // not be the same as |network_thread|. |network_thread| is the thread
   // on which requests will actually be monitored and reported.
   DomainReliabilityMonitor(
       const std::string& upload_reporter_string,
       const DomainReliabilityContext::UploadAllowedCallback&
           upload_allowed_callback,
-      const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
+      const scoped_refptr<base::SingleThreadTaskRunner>& main_thread,
       const scoped_refptr<base::SingleThreadTaskRunner>& network_thread);
 
   // Same, but specifies a mock interface for time functions for testing.
@@ -66,25 +66,25 @@
       const std::string& upload_reporter_string,
       const DomainReliabilityContext::UploadAllowedCallback&
           upload_allowed_callback,
-      const scoped_refptr<base::SingleThreadTaskRunner>& pref_thread,
+      const scoped_refptr<base::SingleThreadTaskRunner>& main_thread,
       const scoped_refptr<base::SingleThreadTaskRunner>& network_thread,
       std::unique_ptr<MockableTime> time);
 
-  // Must be called from the pref thread if |MoveToNetworkThread| was not
+  // Must be called from the main thread if |InitializeOnNetworkThread| was not
   // called, or from the network thread if it was called.
   ~DomainReliabilityMonitor() override;
 
   // Must be called before |InitURLRequestContext| on the same thread on which
   // the Monitor was constructed. Moves (most of) the Monitor to the network
   // thread passed in the constructor.
-  void MoveToNetworkThread();
+  void InitializeOnNetworkThread();
 
   // All public methods below this point must be called on the network thread
-  // after |MoveToNetworkThread| is called on the pref thread.
+  // after |InitializeOnNetworkThread| is called on the main thread.
 
   // Initializes the Monitor's URLRequestContextGetter.
   //
-  // Must be called on the network thread, after |MoveToNetworkThread|.
+  // Must be called on the network thread, after |InitializeOnNetworkThread|.
   void InitURLRequestContext(net::URLRequestContext* url_request_context);
 
   // Same, but for unittests where the Getter is readily available.
@@ -177,8 +177,8 @@
 
   void MaybeHandleHeader(const RequestInfo& info);
 
-  bool OnPrefThread() const {
-    return pref_task_runner_->BelongsToCurrentThread();
+  bool OnMainThread() const {
+    return main_task_runner_->BelongsToCurrentThread();
   }
   bool OnNetworkThread() const {
     return network_task_runner_->BelongsToCurrentThread();
@@ -195,7 +195,7 @@
   std::unique_ptr<DomainReliabilityUploader> uploader_;
   DomainReliabilityContextManager context_manager_;
 
-  scoped_refptr<base::SingleThreadTaskRunner> pref_task_runner_;
+  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
 
   bool moved_to_network_thread_;
diff --git a/components/domain_reliability/monitor_unittest.cc b/components/domain_reliability/monitor_unittest.cc
index d464ece7..c593723 100644
--- a/components/domain_reliability/monitor_unittest.cc
+++ b/components/domain_reliability/monitor_unittest.cc
@@ -54,17 +54,17 @@
   typedef DomainReliabilityMonitor::RequestInfo RequestInfo;
 
   DomainReliabilityMonitorTest()
-      : pref_task_runner_(new base::TestSimpleTaskRunner()),
+      : main_task_runner(new base::TestSimpleTaskRunner()),
         network_task_runner_(new base::TestSimpleTaskRunner()),
         url_request_context_getter_(
             new net::TestURLRequestContextGetter(network_task_runner_)),
         time_(new MockTime()),
         monitor_("test-reporter",
                  DomainReliabilityContext::UploadAllowedCallback(),
-                 pref_task_runner_,
+                 main_task_runner,
                  network_task_runner_,
                  std::unique_ptr<MockableTime>(time_)) {
-    monitor_.MoveToNetworkThread();
+    monitor_.InitializeOnNetworkThread();
     monitor_.InitURLRequestContext(url_request_context_getter_);
     monitor_.SetDiscardUploads(false);
   }
@@ -104,7 +104,7 @@
     return monitor_.AddContextForTesting(std::move(config));
   }
 
-  scoped_refptr<base::TestSimpleTaskRunner> pref_task_runner_;
+  scoped_refptr<base::TestSimpleTaskRunner> main_task_runner;
   scoped_refptr<base::TestSimpleTaskRunner> network_task_runner_;
   scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
   MockTime* time_;
diff --git a/components/domain_reliability/service.cc b/components/domain_reliability/service.cc
index cf8287f..5288017 100644
--- a/components/domain_reliability/service.cc
+++ b/components/domain_reliability/service.cc
@@ -15,11 +15,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/domain_reliability/context.h"
 #include "components/domain_reliability/monitor.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/permission_controller.h"
-#include "content/public/browser/permission_type.h"
 #include "net/url_request/url_request_context_getter.h"
-#include "third_party/blink/public/platform/modules/permissions/permission_status.mojom.h"
 #include "url/gurl.h"
 
 namespace domain_reliability {
@@ -51,11 +47,8 @@
 class DomainReliabilityServiceImpl : public DomainReliabilityService {
  public:
   explicit DomainReliabilityServiceImpl(
-      const std::string& upload_reporter_string,
-      content::BrowserContext* browser_context)
-      : upload_reporter_string_(upload_reporter_string),
-        browser_context_(browser_context),
-        weak_factory_(this) {}
+      const std::string& upload_reporter_string)
+      : upload_reporter_string_(upload_reporter_string), weak_factory_(this) {}
 
   ~DomainReliabilityServiceImpl() override {}
 
@@ -63,18 +56,15 @@
 
   std::unique_ptr<DomainReliabilityMonitor> CreateMonitor(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
-      override {
+      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+      const DomainReliabilityContext::UploadAllowedCallback&
+          upload_allowed_callback) override {
     DCHECK(!network_task_runner_);
 
     std::unique_ptr<DomainReliabilityMonitor> monitor(
-        new DomainReliabilityMonitor(
-            upload_reporter_string_,
-            base::Bind(
-                &DomainReliabilityServiceImpl::CheckUploadAllowedOnIOThread,
-                weak_factory_.GetWeakPtr(), base::RetainedRef(ui_task_runner),
-                base::RetainedRef(network_task_runner)),
-            ui_task_runner, network_task_runner));
+        new DomainReliabilityMonitor(upload_reporter_string_,
+                                     upload_allowed_callback, ui_task_runner,
+                                     network_task_runner));
 
     monitor_ = monitor->MakeWeakPtr();
     network_task_runner_ = network_task_runner;
@@ -133,36 +123,7 @@
   }
 
  private:
-  static void CheckUploadAllowedOnIOThread(
-      base::WeakPtr<DomainReliabilityServiceImpl> service,
-      scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
-      const GURL& origin,
-      base::OnceCallback<void(bool)> callback) {
-    ui_task_runner->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            &DomainReliabilityServiceImpl::CheckUploadAllowedOnUIThread,
-            service, base::RetainedRef(network_task_runner), origin,
-            std::move(callback)));
-  }
-
-  void CheckUploadAllowedOnUIThread(
-      base::SingleThreadTaskRunner* network_task_runner,
-      const GURL& origin,
-      base::OnceCallback<void(bool)> callback) {
-    content::PermissionController* permission_controller =
-        content::BrowserContext::GetPermissionController(browser_context_);
-    DCHECK(permission_controller);
-    bool allowed = permission_controller->GetPermissionStatus(
-                       content::PermissionType::BACKGROUND_SYNC, origin,
-                       origin) == blink::mojom::PermissionStatus::GRANTED;
-    network_task_runner->PostTask(FROM_HERE,
-                                  base::BindOnce(std::move(callback), allowed));
-  }
-
   std::string upload_reporter_string_;
-  content::BrowserContext* browser_context_;
   base::WeakPtr<DomainReliabilityMonitor> monitor_;
   scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
   base::WeakPtrFactory<DomainReliabilityServiceImpl> weak_factory_;
@@ -170,10 +131,8 @@
 
 // static
 DomainReliabilityService* DomainReliabilityService::Create(
-    const std::string& upload_reporter_string,
-    content::BrowserContext* browser_context) {
-  return new DomainReliabilityServiceImpl(upload_reporter_string,
-                                          browser_context);
+    const std::string& upload_reporter_string) {
+  return new DomainReliabilityServiceImpl(upload_reporter_string);
 }
 
 DomainReliabilityService::~DomainReliabilityService() {}
diff --git a/components/domain_reliability/service.h b/components/domain_reliability/service.h
index e2ac25d..78f76dda 100644
--- a/components/domain_reliability/service.h
+++ b/components/domain_reliability/service.h
@@ -16,7 +16,6 @@
 #include "components/domain_reliability/config.h"
 #include "components/domain_reliability/context.h"
 #include "components/domain_reliability/domain_reliability_export.h"
-#include "components/keyed_service/core/keyed_service.h"
 
 class GURL;
 
@@ -24,10 +23,6 @@
 class Value;
 }  // namespace base
 
-namespace content {
-class BrowserContext;
-}  // namespace content
-
 namespace net {
 class URLRequestContextGetter;
 }  // namespace net
@@ -36,29 +31,30 @@
 
 class DomainReliabilityMonitor;
 
-// DomainReliabilityService is a KeyedService that manages a Monitor that lives
-// on another thread (as provided by the URLRequestContextGetter's task runner)
-// and proxies (selected) method calls to it. Destruction of the Monitor (on
-// that thread) is the responsibility of the caller.
-class DOMAIN_RELIABILITY_EXPORT DomainReliabilityService
-    : public KeyedService {
+// DomainReliabilityService manages a Monitor that (could) live on another
+// thread (as provided by the URLRequestContextGetter's task runner) and
+// proxies (selected) method calls to it. Destruction of the Monitor (on that
+// thread) is the responsibility of the caller.
+class DOMAIN_RELIABILITY_EXPORT DomainReliabilityService {
  public:
   // Creates a DomainReliabilityService that will contain a Monitor with the
   // given upload reporter string.
   static DomainReliabilityService* Create(
-      const std::string& upload_reporter_string,
-      content::BrowserContext* browser_context);
+      const std::string& upload_reporter_string);
 
-  ~DomainReliabilityService() override;
+  virtual ~DomainReliabilityService();
 
   // Initializes the Service: given the task runner on which Monitor methods
   // should be called, creates the Monitor and returns it. Can be called at
   // most once, and must be called before any of the below methods can be
   // called. The caller is responsible for destroying the Monitor on the given
-  // task runner when it is no longer needed.
+  // task runner when it is no longer needed. |upload_allowed_callback| is
+  // called on the network thread before every upload to check if it's allowed.
   virtual std::unique_ptr<DomainReliabilityMonitor> CreateMonitor(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner) = 0;
+      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+      const DomainReliabilityContext::UploadAllowedCallback&
+          upload_allowed_callback) = 0;
 
   // Clears browsing data on the associated Monitor. |Init()| must have been
   // called first.
diff --git a/components/domain_reliability/service_unittest.cc b/components/domain_reliability/service_unittest.cc
index 7ad37b5..3b1512e 100644
--- a/components/domain_reliability/service_unittest.cc
+++ b/components/domain_reliability/service_unittest.cc
@@ -8,153 +8,35 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/task/post_task.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
 #include "components/domain_reliability/monitor.h"
 #include "components/domain_reliability/test_util.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/permission_controller.h"
-#include "content/public/browser/permission_controller_delegate.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/test_browser_context.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "content/public/test/test_utils.h"
 #include "net/base/host_port_pair.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/platform/modules/permissions/permission_status.mojom.h"
 
 namespace domain_reliability {
 
-namespace {
-
-class TestPermissionManager : public content::PermissionControllerDelegate {
- public:
-  TestPermissionManager() : get_permission_status_count_(0) {}
-
-  int get_permission_status_count() const {
-    return get_permission_status_count_;
-  }
-  content::PermissionType last_permission() const { return last_permission_; }
-  const GURL& last_requesting_origin() const { return last_requesting_origin_; }
-  const GURL& last_embedding_origin() const { return last_embedding_origin_; }
-
-  void set_permission_status(blink::mojom::PermissionStatus permission_status) {
-    permission_status_ = permission_status;
-  }
-
-  // content::PermissionManager:
-
-  ~TestPermissionManager() override {}
-
-  blink::mojom::PermissionStatus GetPermissionStatus(
-      content::PermissionType permission,
-      const GURL& requesting_origin,
-      const GURL& embedding_origin) override {
-    ++get_permission_status_count_;
-
-    last_permission_ = permission;
-    last_requesting_origin_ = requesting_origin;
-    last_embedding_origin_ = embedding_origin;
-
-    return permission_status_;
-  }
-
-  blink::mojom::PermissionStatus GetPermissionStatusForFrame(
-      content::PermissionType permission,
-      content::RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin) override {
-    return GetPermissionStatus(
-        permission, requesting_origin,
-        content::WebContents::FromRenderFrameHost(render_frame_host)
-            ->GetLastCommittedURL()
-            .GetOrigin());
-  }
-
-  int RequestPermission(
-      content::PermissionType permission,
-      content::RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin,
-      bool user_gesture,
-      const base::Callback<void(blink::mojom::PermissionStatus)>& callback)
-      override {
-    NOTIMPLEMENTED();
-    return content::PermissionController::kNoPendingOperation;
-  }
-
-  int RequestPermissions(
-      const std::vector<content::PermissionType>& permission,
-      content::RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin,
-      bool user_gesture,
-      const base::Callback<
-          void(const std::vector<blink::mojom::PermissionStatus>&)>& callback)
-      override {
-    NOTIMPLEMENTED();
-    return content::PermissionController::kNoPendingOperation;
-  }
-
-  void ResetPermission(content::PermissionType permission,
-                       const GURL& requesting_origin,
-                       const GURL& embedding_origin) override {
-    NOTIMPLEMENTED();
-  }
-
-  int SubscribePermissionStatusChange(
-      content::PermissionType permission,
-      content::RenderFrameHost* render_frame_host,
-      const GURL& requesting_origin,
-      const base::Callback<void(blink::mojom::PermissionStatus)>& callback)
-      override {
-    NOTIMPLEMENTED();
-    return 0;
-  }
-
-  void UnsubscribePermissionStatusChange(int subscription_id) override {
-    NOTIMPLEMENTED();
-  }
-
- private:
-  // Number of calls made to GetPermissionStatus.
-  int get_permission_status_count_;
-
-  // Parameters to last call to GetPermissionStatus:
-
-  content::PermissionType last_permission_;
-  GURL last_requesting_origin_;
-  GURL last_embedding_origin_;
-
-  // Value to return from GetPermissionStatus.
-  blink::mojom::PermissionStatus permission_status_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestPermissionManager);
-};
-
-}  // namespace
 
 class DomainReliabilityServiceTest : public testing::Test {
  protected:
   using RequestInfo = DomainReliabilityMonitor::RequestInfo;
 
-  DomainReliabilityServiceTest()
-      : upload_reporter_string_("test"),
-        permission_manager_(new TestPermissionManager()) {
-    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner =
-        base::CreateSingleThreadTaskRunnerWithTraits(
-            {content::BrowserThread::UI});
-    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner =
-        base::CreateSingleThreadTaskRunnerWithTraits(
-            {content::BrowserThread::IO});
-    url_request_context_getter_ =
-        new net::TestURLRequestContextGetter(network_task_runner);
-    browser_context_.SetPermissionControllerDelegate(
-        base::WrapUnique(permission_manager_));
-    service_ = base::WrapUnique(DomainReliabilityService::Create(
-        upload_reporter_string_, &browser_context_));
-    monitor_ = service_->CreateMonitor(ui_task_runner, network_task_runner);
-    monitor_->MoveToNetworkThread();
+  DomainReliabilityServiceTest() : upload_reporter_string_("test") {
+    url_request_context_getter_ = new net::TestURLRequestContextGetter(
+        scoped_task_environment_.GetMainThreadTaskRunner());
+    service_ = base::WrapUnique(
+        DomainReliabilityService::Create(upload_reporter_string_));
+    monitor_ = service_->CreateMonitor(
+        scoped_task_environment_.GetMainThreadTaskRunner(),
+        scoped_task_environment_.GetMainThreadTaskRunner(),
+        base::BindRepeating(
+            &DomainReliabilityServiceTest::CheckDomainReliablityUploadAllowed,
+            base::Unretained(this)));
+    monitor_->InitializeOnNetworkThread();
     monitor_->InitURLRequestContext(url_request_context_getter_);
     monitor_->SetDiscardUploads(true);
   }
@@ -172,15 +54,14 @@
     return monitor_->uploader_->GetDiscardedUploadCount();
   }
 
-  content::TestBrowserThreadBundle thread_bundle_;
+  void set_upload_allowed(bool allowed) { upload_allowed_ = allowed; }
+
+  int upload_check_count() { return upload_check_count_; }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
 
   std::string upload_reporter_string_;
 
-  content::TestBrowserContext browser_context_;
-
-  // Owned by browser_context_, not the test class.
-  TestPermissionManager* permission_manager_;
-
   std::unique_ptr<DomainReliabilityService> service_;
 
   scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
@@ -188,6 +69,16 @@
   std::unique_ptr<DomainReliabilityMonitor> monitor_;
 
  private:
+  void CheckDomainReliablityUploadAllowed(
+      const GURL& origin,
+      base::OnceCallback<void(bool)> callback) {
+    ++upload_check_count_;
+    std::move(callback).Run(upload_allowed_);
+  }
+
+  bool upload_allowed_ = false;
+  int upload_check_count_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(DomainReliabilityServiceTest);
 };
 
@@ -196,8 +87,7 @@
 TEST_F(DomainReliabilityServiceTest, Create) {}
 
 TEST_F(DomainReliabilityServiceTest, UploadAllowed) {
-  permission_manager_->set_permission_status(
-      blink::mojom::PermissionStatus::GRANTED);
+  set_upload_allowed(true);
 
   monitor_->AddContextForTesting(
       MakeTestConfigWithOrigin(GURL("https://example/")));
@@ -219,16 +109,13 @@
 
   monitor_->ForceUploadsForTesting();
 
-  content::RunAllPendingInMessageLoop(content::BrowserThread::UI);
-  EXPECT_EQ(1, permission_manager_->get_permission_status_count());
-
-  content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(1, upload_check_count());
   EXPECT_EQ(1, GetDiscardedUploadCount());
 }
 
 TEST_F(DomainReliabilityServiceTest, UploadForbidden) {
-  permission_manager_->set_permission_status(
-      blink::mojom::PermissionStatus::DENIED);
+  set_upload_allowed(false);
 
   monitor_->AddContextForTesting(
       MakeTestConfigWithOrigin(GURL("https://example/")));
@@ -250,16 +137,13 @@
 
   monitor_->ForceUploadsForTesting();
 
-  content::RunAllPendingInMessageLoop(content::BrowserThread::UI);
-  EXPECT_EQ(1, permission_manager_->get_permission_status_count());
-
-  content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(1, upload_check_count());
   EXPECT_EQ(0, GetDiscardedUploadCount());
 }
 
 TEST_F(DomainReliabilityServiceTest, MonitorDestroyedBeforeCheckRuns) {
-  permission_manager_->set_permission_status(
-      blink::mojom::PermissionStatus::DENIED);
+  set_upload_allowed(false);
 
   monitor_->AddContextForTesting(
       MakeTestConfigWithOrigin(GURL("https://example/")));
@@ -284,16 +168,13 @@
   monitor_->Shutdown();
   monitor_.reset();
 
-  content::RunAllPendingInMessageLoop(content::BrowserThread::UI);
-  EXPECT_EQ(1, permission_manager_->get_permission_status_count());
-
-  content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(1, upload_check_count());
   // Makes no sense to check upload count, since monitor was destroyed.
 }
 
 TEST_F(DomainReliabilityServiceTest, MonitorDestroyedBeforeCheckReturns) {
-  permission_manager_->set_permission_status(
-      blink::mojom::PermissionStatus::DENIED);
+  set_upload_allowed(false);
 
   monitor_->AddContextForTesting(
       MakeTestConfigWithOrigin(GURL("https://example/")));
@@ -315,13 +196,13 @@
 
   monitor_->ForceUploadsForTesting();
 
-  content::RunAllPendingInMessageLoop(content::BrowserThread::UI);
-  EXPECT_EQ(1, permission_manager_->get_permission_status_count());
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(1, upload_check_count());
 
   monitor_->Shutdown();
   monitor_.reset();
 
-  content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
+  scoped_task_environment_.RunUntilIdle();
   // Makes no sense to check upload count, since monitor was destroyed.
 }
 
diff --git a/components/domain_reliability/uploader.cc b/components/domain_reliability/uploader.cc
index 1f109a5..3f7f0687d 100644
--- a/components/domain_reliability/uploader.cc
+++ b/components/domain_reliability/uploader.cc
@@ -76,14 +76,14 @@
       int max_upload_depth,
       const GURL& upload_url,
       const DomainReliabilityUploader::UploadCallback& callback) override {
-    VLOG(1) << "Uploading report to " << upload_url;
-    VLOG(2) << "Report JSON: " << report_json;
+    DVLOG(1) << "Uploading report to " << upload_url;
+    DVLOG(2) << "Report JSON: " << report_json;
 
     if (discard_uploads_)
       discarded_upload_count_++;
 
     if (discard_uploads_ || shutdown_) {
-      VLOG(1) << "Discarding report instead of uploading.";
+      DVLOG(1) << "Discarding report instead of uploading.";
       UploadResult result;
       result.status = UploadResult::SUCCESS;
       callback.Run(result);
@@ -134,7 +134,7 @@
 
   void SetDiscardUploads(bool discard_uploads) override {
     discard_uploads_ = discard_uploads;
-    VLOG(1) << "Setting discard_uploads to " << discard_uploads;
+    DVLOG(1) << "Setting discard_uploads to " << discard_uploads;
   }
 
   void Shutdown() override {
@@ -169,9 +169,9 @@
       }
     }
 
-    VLOG(1) << "Upload finished with net error " << net_error
-            << ", response code " << http_response_code
-            << ", retry after " << retry_after;
+    DVLOG(1) << "Upload finished with net error " << net_error
+             << ", response code " << http_response_code << ", retry after "
+             << retry_after;
 
     base::UmaHistogramSparse("DomainReliability.UploadResponseCode",
                              http_response_code);
diff --git a/components/exo/keyboard_delegate.h b/components/exo/keyboard_delegate.h
index 3d7d6941..f918ec5f 100644
--- a/components/exo/keyboard_delegate.h
+++ b/components/exo/keyboard_delegate.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_EXO_KEYBOARD_DELEGATE_H_
 #define COMPONENTS_EXO_KEYBOARD_DELEGATE_H_
 
+#include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/time/time.h"
 
diff --git a/components/exo/wayland/BUILD.gn b/components/exo/wayland/BUILD.gn
index 86bb3ed..e65f3a5 100644
--- a/components/exo/wayland/BUILD.gn
+++ b/components/exo/wayland/BUILD.gn
@@ -37,6 +37,10 @@
     "server_util.h",
     "wayland_input_delegate.cc",
     "wayland_input_delegate.h",
+    "wayland_keyboard_delegate.cc",
+    "wayland_keyboard_delegate.h",
+    "wayland_pointer_delegate.cc",
+    "wayland_pointer_delegate.h",
     "zcr_notification_shell.cc",
     "zcr_notification_shell.h",
     "zwp_text_input_manager.cc",
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc
index 3361faeb..6db47b2 100644
--- a/components/exo/wayland/server.cc
+++ b/components/exo/wayland/server.cc
@@ -96,6 +96,8 @@
 #include "components/exo/touch_stylus_delegate.h"
 #include "components/exo/wayland/server_util.h"
 #include "components/exo/wayland/wayland_input_delegate.h"
+#include "components/exo/wayland/wayland_keyboard_delegate.h"
+#include "components/exo/wayland/wayland_pointer_delegate.h"
 #include "components/exo/wayland/zcr_notification_shell.h"
 #include "components/exo/wayland/zwp_text_input_manager.h"
 #include "components/exo/wm_helper.h"
@@ -3559,133 +3561,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 // wl_pointer_interface:
 
-// Pointer delegate class that accepts events for surfaces owned by the same
-// client as a pointer resource.
-class WaylandPointerDelegate : public WaylandInputDelegate,
-                               public PointerDelegate {
- public:
-  explicit WaylandPointerDelegate(wl_resource* pointer_resource)
-      : pointer_resource_(pointer_resource) {}
-
-  // Overridden from PointerDelegate:
-  void OnPointerDestroying(Pointer* pointer) override { delete this; }
-  bool CanAcceptPointerEventsForSurface(Surface* surface) const override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    // We can accept events for this surface if the client is the same as the
-    // pointer.
-    return surface_resource &&
-           wl_resource_get_client(surface_resource) == client();
-  }
-  void OnPointerEnter(Surface* surface,
-                      const gfx::PointF& location,
-                      int button_flags) override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    DCHECK(surface_resource);
-    // Should we be sending button events to the client before the enter event
-    // if client's pressed button state is different from |button_flags|?
-    wl_pointer_send_enter(pointer_resource_, next_serial(), surface_resource,
-                          wl_fixed_from_double(location.x()),
-                          wl_fixed_from_double(location.y()));
-  }
-  void OnPointerLeave(Surface* surface) override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    DCHECK(surface_resource);
-    wl_pointer_send_leave(pointer_resource_, next_serial(), surface_resource);
-  }
-  void OnPointerMotion(base::TimeTicks time_stamp,
-                       const gfx::PointF& location) override {
-    SendTimestamp(time_stamp);
-    wl_pointer_send_motion(
-        pointer_resource_, TimeTicksToMilliseconds(time_stamp),
-        wl_fixed_from_double(location.x()), wl_fixed_from_double(location.y()));
-  }
-  void OnPointerButton(base::TimeTicks time_stamp,
-                       int button_flags,
-                       bool pressed) override {
-    struct {
-      ui::EventFlags flag;
-      uint32_t value;
-    } buttons[] = {
-        {ui::EF_LEFT_MOUSE_BUTTON, BTN_LEFT},
-        {ui::EF_RIGHT_MOUSE_BUTTON, BTN_RIGHT},
-        {ui::EF_MIDDLE_MOUSE_BUTTON, BTN_MIDDLE},
-        {ui::EF_FORWARD_MOUSE_BUTTON, BTN_FORWARD},
-        {ui::EF_BACK_MOUSE_BUTTON, BTN_BACK},
-    };
-    uint32_t serial = next_serial();
-    for (auto button : buttons) {
-      if (button_flags & button.flag) {
-        SendTimestamp(time_stamp);
-        wl_pointer_send_button(
-            pointer_resource_, serial, TimeTicksToMilliseconds(time_stamp),
-            button.value, pressed ? WL_POINTER_BUTTON_STATE_PRESSED
-                                  : WL_POINTER_BUTTON_STATE_RELEASED);
-      }
-    }
-  }
-  void OnPointerScroll(base::TimeTicks time_stamp,
-                       const gfx::Vector2dF& offset,
-                       bool discrete) override {
-    // Same as Weston, the reference compositor.
-    const double kAxisStepDistance = 10.0 / ui::MouseWheelEvent::kWheelDelta;
-
-    if (wl_resource_get_version(pointer_resource_) >=
-        WL_POINTER_AXIS_SOURCE_SINCE_VERSION) {
-      int32_t axis_source = discrete ? WL_POINTER_AXIS_SOURCE_WHEEL
-                                     : WL_POINTER_AXIS_SOURCE_FINGER;
-      wl_pointer_send_axis_source(pointer_resource_, axis_source);
-    }
-
-    double x_value = offset.x() * kAxisStepDistance;
-    SendTimestamp(time_stamp);
-    wl_pointer_send_axis(pointer_resource_, TimeTicksToMilliseconds(time_stamp),
-                         WL_POINTER_AXIS_HORIZONTAL_SCROLL,
-                         wl_fixed_from_double(-x_value));
-
-    double y_value = offset.y() * kAxisStepDistance;
-    SendTimestamp(time_stamp);
-    wl_pointer_send_axis(pointer_resource_, TimeTicksToMilliseconds(time_stamp),
-                         WL_POINTER_AXIS_VERTICAL_SCROLL,
-                         wl_fixed_from_double(-y_value));
-  }
-  void OnPointerScrollStop(base::TimeTicks time_stamp) override {
-    if (wl_resource_get_version(pointer_resource_) >=
-        WL_POINTER_AXIS_STOP_SINCE_VERSION) {
-      SendTimestamp(time_stamp);
-      wl_pointer_send_axis_stop(pointer_resource_,
-                                TimeTicksToMilliseconds(time_stamp),
-                                WL_POINTER_AXIS_HORIZONTAL_SCROLL);
-      SendTimestamp(time_stamp);
-      wl_pointer_send_axis_stop(pointer_resource_,
-                                TimeTicksToMilliseconds(time_stamp),
-                                WL_POINTER_AXIS_VERTICAL_SCROLL);
-    }
-  }
-  void OnPointerFrame() override {
-    if (wl_resource_get_version(pointer_resource_) >=
-        WL_POINTER_FRAME_SINCE_VERSION) {
-      wl_pointer_send_frame(pointer_resource_);
-    }
-    wl_client_flush(client());
-  }
-
- private:
-  // The client who own this pointer instance.
-  wl_client* client() const {
-    return wl_resource_get_client(pointer_resource_);
-  }
-
-  // Returns the next serial to use for pointer events.
-  uint32_t next_serial() const {
-    return wl_display_next_serial(wl_client_get_display(client()));
-  }
-
-  // The pointer resource associated with the pointer.
-  wl_resource* const pointer_resource_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaylandPointerDelegate);
-};
-
 void pointer_set_cursor(wl_client* client,
                         wl_resource* resource,
                         uint32_t serial,
@@ -3708,195 +3583,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 // wl_keyboard_interface:
 
-// Keyboard delegate class that accepts events for surfaces owned by the same
-// client as a keyboard resource.
-class WaylandKeyboardDelegate : public WaylandInputDelegate,
-                                public KeyboardDelegate,
-                                public KeyboardObserver
-#if defined(OS_CHROMEOS)
-    ,
-                                public ash::ImeController::Observer
-#endif
-{
-#if BUILDFLAG(USE_XKBCOMMON)
- public:
-  explicit WaylandKeyboardDelegate(wl_resource* keyboard_resource)
-      : keyboard_resource_(keyboard_resource),
-        xkb_context_(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) {
-#if defined(OS_CHROMEOS)
-    ash::ImeController* ime_controller = ash::Shell::Get()->ime_controller();
-    ime_controller->AddObserver(this);
-    SendNamedLayout(ime_controller->keyboard_layout_name());
-#else
-    SendLayout(nullptr);
-#endif
-  }
-#if defined(OS_CHROMEOS)
-  ~WaylandKeyboardDelegate() override {
-    ash::Shell::Get()->ime_controller()->RemoveObserver(this);
-  }
-#endif
-
-  // Overridden from KeyboardDelegate:
-  void OnKeyboardDestroying(Keyboard* keyboard) override { delete this; }
-  bool CanAcceptKeyboardEventsForSurface(Surface* surface) const override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    // We can accept events for this surface if the client is the same as the
-    // keyboard.
-    return surface_resource &&
-           wl_resource_get_client(surface_resource) == client();
-  }
-  void OnKeyboardEnter(
-      Surface* surface,
-      const base::flat_map<ui::DomCode, ui::DomCode>& pressed_keys) override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    DCHECK(surface_resource);
-    wl_array keys;
-    wl_array_init(&keys);
-    for (const auto& entry : pressed_keys) {
-      uint32_t* value =
-          static_cast<uint32_t*>(wl_array_add(&keys, sizeof(uint32_t)));
-      DCHECK(value);
-      *value = DomCodeToKey(entry.second);
-    }
-    wl_keyboard_send_enter(keyboard_resource_, next_serial(), surface_resource,
-                           &keys);
-    wl_array_release(&keys);
-    wl_client_flush(client());
-  }
-  void OnKeyboardLeave(Surface* surface) override {
-    wl_resource* surface_resource = GetSurfaceResource(surface);
-    DCHECK(surface_resource);
-    wl_keyboard_send_leave(keyboard_resource_, next_serial(), surface_resource);
-    wl_client_flush(client());
-  }
-  uint32_t OnKeyboardKey(base::TimeTicks time_stamp,
-                         ui::DomCode key,
-                         bool pressed) override {
-    uint32_t serial = next_serial();
-    SendTimestamp(time_stamp);
-    wl_keyboard_send_key(keyboard_resource_, serial,
-                         TimeTicksToMilliseconds(time_stamp), DomCodeToKey(key),
-                         pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
-                                 : WL_KEYBOARD_KEY_STATE_RELEASED);
-    wl_client_flush(client());
-    return serial;
-  }
-  void OnKeyboardModifiers(int modifier_flags) override {
-    xkb_state_update_mask(xkb_state_.get(),
-                          ModifierFlagsToXkbModifiers(modifier_flags), 0, 0, 0,
-                          0, 0);
-    wl_keyboard_send_modifiers(
-        keyboard_resource_, next_serial(),
-        xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_DEPRESSED),
-        xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_LOCKED),
-        xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_LATCHED),
-        xkb_state_serialize_layout(xkb_state_.get(),
-                                   XKB_STATE_LAYOUT_EFFECTIVE));
-    wl_client_flush(client());
-  }
-
-#if defined(OS_CHROMEOS)
-  // Overridden from ImeController::Observer:
-  void OnCapsLockChanged(bool enabled) override {}
-  void OnKeyboardLayoutNameChanged(const std::string& layout_name) override {
-    SendNamedLayout(layout_name);
-  }
-#endif
-
- private:
-  // Returns the corresponding key given a dom code.
-  uint32_t DomCodeToKey(ui::DomCode code) const {
-    // This assumes KeycodeConverter has been built with evdev/xkb codes.
-    xkb_keycode_t xkb_keycode = static_cast<xkb_keycode_t>(
-        ui::KeycodeConverter::DomCodeToNativeKeycode(code));
-
-    // Keycodes are offset by 8 in Xkb.
-    DCHECK_GE(xkb_keycode, 8u);
-    return xkb_keycode - 8;
-  }
-
-  // Returns a set of Xkb modififers given a set of modifier flags.
-  uint32_t ModifierFlagsToXkbModifiers(int modifier_flags) {
-    struct {
-      ui::EventFlags flag;
-      const char* xkb_name;
-    } modifiers[] = {
-        {ui::EF_SHIFT_DOWN, XKB_MOD_NAME_SHIFT},
-        {ui::EF_CONTROL_DOWN, XKB_MOD_NAME_CTRL},
-        {ui::EF_ALT_DOWN, XKB_MOD_NAME_ALT},
-        {ui::EF_COMMAND_DOWN, XKB_MOD_NAME_LOGO},
-        {ui::EF_ALTGR_DOWN, "Mod5"},
-        {ui::EF_MOD3_DOWN, "Mod3"},
-        {ui::EF_NUM_LOCK_ON, XKB_MOD_NAME_NUM},
-        {ui::EF_CAPS_LOCK_ON, XKB_MOD_NAME_CAPS},
-    };
-    uint32_t xkb_modifiers = 0;
-    for (auto modifier : modifiers) {
-      if (modifier_flags & modifier.flag) {
-        xkb_modifiers |=
-            1 << xkb_keymap_mod_get_index(xkb_keymap_.get(), modifier.xkb_name);
-      }
-    }
-    return xkb_modifiers;
-  }
-
-#if defined(OS_CHROMEOS)
-  // Send the named keyboard layout to the client.
-  void SendNamedLayout(const std::string& layout_name) {
-    std::string layout_id, layout_variant;
-    ui::XkbKeyboardLayoutEngine::ParseLayoutName(layout_name, &layout_id,
-                                                 &layout_variant);
-    xkb_rule_names names = {.rules = nullptr,
-                            .model = "pc101",
-                            .layout = layout_id.c_str(),
-                            .variant = layout_variant.c_str(),
-                            .options = ""};
-    SendLayout(&names);
-  }
-#endif
-
-  // Send the keyboard layout named by XKB rules to the client.
-  void SendLayout(const xkb_rule_names* names) {
-    xkb_keymap_.reset(xkb_keymap_new_from_names(xkb_context_.get(), names,
-                                                XKB_KEYMAP_COMPILE_NO_FLAGS));
-    xkb_state_.reset(xkb_state_new(xkb_keymap_.get()));
-    std::unique_ptr<char, base::FreeDeleter> keymap_string(
-        xkb_keymap_get_as_string(xkb_keymap_.get(), XKB_KEYMAP_FORMAT_TEXT_V1));
-    DCHECK(keymap_string.get());
-    size_t keymap_size = strlen(keymap_string.get()) + 1;
-    base::SharedMemory shared_keymap;
-    bool rv = shared_keymap.CreateAndMapAnonymous(keymap_size);
-    DCHECK(rv);
-    memcpy(shared_keymap.memory(), keymap_string.get(), keymap_size);
-    wl_keyboard_send_keymap(keyboard_resource_,
-                            WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
-                            shared_keymap.handle().GetHandle(), keymap_size);
-    wl_client_flush(client());
-  }
-
-  // The client who own this keyboard instance.
-  wl_client* client() const {
-    return wl_resource_get_client(keyboard_resource_);
-  }
-
-  // Returns the next serial to use for keyboard events.
-  uint32_t next_serial() const {
-    return wl_display_next_serial(wl_client_get_display(client()));
-  }
-
-  // The keyboard resource associated with the keyboard.
-  wl_resource* const keyboard_resource_;
-
-  // The Xkb state used for the keyboard.
-  std::unique_ptr<xkb_context, ui::XkbContextDeleter> xkb_context_;
-  std::unique_ptr<xkb_keymap, ui::XkbKeymapDeleter> xkb_keymap_;
-  std::unique_ptr<xkb_state, ui::XkbStateDeleter> xkb_state_;
-
-  DISALLOW_COPY_AND_ASSIGN(WaylandKeyboardDelegate);
-#endif
-};
-
 #if BUILDFLAG(USE_XKBCOMMON)
 
 void keyboard_release(wl_client* client, wl_resource* resource) {
diff --git a/components/exo/wayland/wayland_keyboard_delegate.cc b/components/exo/wayland/wayland_keyboard_delegate.cc
new file mode 100644
index 0000000..f0a65de
--- /dev/null
+++ b/components/exo/wayland/wayland_keyboard_delegate.cc
@@ -0,0 +1,186 @@
+// 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 "components/exo/wayland/wayland_keyboard_delegate.h"
+
+#include <wayland-server-core.h>
+#include <wayland-server-protocol-core.h>
+
+#include "base/containers/flat_map.h"
+
+namespace exo {
+namespace wayland {
+
+#if BUILDFLAG(USE_XKBCOMMON)
+
+WaylandKeyboardDelegate::WaylandKeyboardDelegate(wl_resource* keyboard_resource)
+    : keyboard_resource_(keyboard_resource),
+      xkb_context_(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) {
+#if defined(OS_CHROMEOS)
+  ash::ImeController* ime_controller = ash::Shell::Get()->ime_controller();
+  ime_controller->AddObserver(this);
+  SendNamedLayout(ime_controller->keyboard_layout_name());
+#else
+  SendLayout(nullptr);
+#endif
+}
+
+#if defined(OS_CHROMEOS)
+WaylandKeyboardDelegate::~WaylandKeyboardDelegate() {
+  ash::Shell::Get()->ime_controller()->RemoveObserver(this);
+}
+#endif
+
+void WaylandKeyboardDelegate::OnKeyboardDestroying(Keyboard* keyboard) {
+  delete this;
+}
+
+bool WaylandKeyboardDelegate::CanAcceptKeyboardEventsForSurface(
+    Surface* surface) const {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  // We can accept events for this surface if the client is the same as the
+  // keyboard.
+  return surface_resource &&
+         wl_resource_get_client(surface_resource) == client();
+}
+
+void WaylandKeyboardDelegate::OnKeyboardEnter(
+    Surface* surface,
+    const base::flat_map<ui::DomCode, ui::DomCode>& pressed_keys) {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  DCHECK(surface_resource);
+  wl_array keys;
+  wl_array_init(&keys);
+  for (const auto& entry : pressed_keys) {
+    uint32_t* value =
+        static_cast<uint32_t*>(wl_array_add(&keys, sizeof(uint32_t)));
+    DCHECK(value);
+    *value = DomCodeToKey(entry.second);
+  }
+  wl_keyboard_send_enter(keyboard_resource_, next_serial(), surface_resource,
+                         &keys);
+  wl_array_release(&keys);
+  wl_client_flush(client());
+}
+
+void WaylandKeyboardDelegate::OnKeyboardLeave(Surface* surface) {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  DCHECK(surface_resource);
+  wl_keyboard_send_leave(keyboard_resource_, next_serial(), surface_resource);
+  wl_client_flush(client());
+}
+
+uint32_t WaylandKeyboardDelegate::OnKeyboardKey(base::TimeTicks time_stamp,
+                                                ui::DomCode key,
+                                                bool pressed) {
+  uint32_t serial = next_serial();
+  SendTimestamp(time_stamp);
+  wl_keyboard_send_key(
+      keyboard_resource_, serial, TimeTicksToMilliseconds(time_stamp),
+      DomCodeToKey(key),
+      pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED);
+  wl_client_flush(client());
+  return serial;
+}
+
+void WaylandKeyboardDelegate::OnKeyboardModifiers(int modifier_flags) {
+  xkb_state_update_mask(xkb_state_.get(),
+                        ModifierFlagsToXkbModifiers(modifier_flags), 0, 0, 0, 0,
+                        0);
+  wl_keyboard_send_modifiers(
+      keyboard_resource_, next_serial(),
+      xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_DEPRESSED),
+      xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_LOCKED),
+      xkb_state_serialize_mods(xkb_state_.get(), XKB_STATE_MODS_LATCHED),
+      xkb_state_serialize_layout(xkb_state_.get(), XKB_STATE_LAYOUT_EFFECTIVE));
+  wl_client_flush(client());
+}
+
+#if defined(OS_CHROMEOS)
+void WaylandKeyboardDelegate::OnCapsLockChanged(bool enabled) {}
+
+void WaylandKeyboardDelegate::OnKeyboardLayoutNameChanged(
+    const std::string& layout_name) {
+  SendNamedLayout(layout_name);
+}
+#endif
+
+uint32_t WaylandKeyboardDelegate::DomCodeToKey(ui::DomCode code) const {
+  // This assumes KeycodeConverter has been built with evdev/xkb codes.
+  xkb_keycode_t xkb_keycode = static_cast<xkb_keycode_t>(
+      ui::KeycodeConverter::DomCodeToNativeKeycode(code));
+
+  // Keycodes are offset by 8 in Xkb.
+  DCHECK_GE(xkb_keycode, 8u);
+  return xkb_keycode - 8;
+}
+
+uint32_t WaylandKeyboardDelegate::ModifierFlagsToXkbModifiers(
+    int modifier_flags) {
+  struct {
+    ui::EventFlags flag;
+    const char* xkb_name;
+  } modifiers[] = {
+      {ui::EF_SHIFT_DOWN, XKB_MOD_NAME_SHIFT},
+      {ui::EF_CONTROL_DOWN, XKB_MOD_NAME_CTRL},
+      {ui::EF_ALT_DOWN, XKB_MOD_NAME_ALT},
+      {ui::EF_COMMAND_DOWN, XKB_MOD_NAME_LOGO},
+      {ui::EF_ALTGR_DOWN, "Mod5"},
+      {ui::EF_MOD3_DOWN, "Mod3"},
+      {ui::EF_NUM_LOCK_ON, XKB_MOD_NAME_NUM},
+      {ui::EF_CAPS_LOCK_ON, XKB_MOD_NAME_CAPS},
+  };
+  uint32_t xkb_modifiers = 0;
+  for (auto modifier : modifiers) {
+    if (modifier_flags & modifier.flag) {
+      xkb_modifiers |=
+          1 << xkb_keymap_mod_get_index(xkb_keymap_.get(), modifier.xkb_name);
+    }
+  }
+  return xkb_modifiers;
+}
+
+#if defined(OS_CHROMEOS)
+void WaylandKeyboardDelegate::SendNamedLayout(const std::string& layout_name) {
+  std::string layout_id, layout_variant;
+  ui::XkbKeyboardLayoutEngine::ParseLayoutName(layout_name, &layout_id,
+                                               &layout_variant);
+  xkb_rule_names names = {.rules = nullptr,
+                          .model = "pc101",
+                          .layout = layout_id.c_str(),
+                          .variant = layout_variant.c_str(),
+                          .options = ""};
+  SendLayout(&names);
+}
+#endif
+
+void WaylandKeyboardDelegate::SendLayout(const xkb_rule_names* names) {
+  xkb_keymap_.reset(xkb_keymap_new_from_names(xkb_context_.get(), names,
+                                              XKB_KEYMAP_COMPILE_NO_FLAGS));
+  xkb_state_.reset(xkb_state_new(xkb_keymap_.get()));
+  std::unique_ptr<char, base::FreeDeleter> keymap_string(
+      xkb_keymap_get_as_string(xkb_keymap_.get(), XKB_KEYMAP_FORMAT_TEXT_V1));
+  DCHECK(keymap_string.get());
+  size_t keymap_size = strlen(keymap_string.get()) + 1;
+  base::SharedMemory shared_keymap;
+  bool rv = shared_keymap.CreateAndMapAnonymous(keymap_size);
+  DCHECK(rv);
+  memcpy(shared_keymap.memory(), keymap_string.get(), keymap_size);
+  wl_keyboard_send_keymap(keyboard_resource_, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
+                          shared_keymap.handle().GetHandle(), keymap_size);
+  wl_client_flush(client());
+}
+
+wl_client* WaylandKeyboardDelegate::client() const {
+  return wl_resource_get_client(keyboard_resource_);
+}
+
+uint32_t WaylandKeyboardDelegate::next_serial() const {
+  return wl_display_next_serial(wl_client_get_display(client()));
+}
+
+#endif
+
+}  // namespace wayland
+}  // namespace exo
diff --git a/components/exo/wayland/wayland_keyboard_delegate.h b/components/exo/wayland/wayland_keyboard_delegate.h
new file mode 100644
index 0000000..5ca3084
--- /dev/null
+++ b/components/exo/wayland/wayland_keyboard_delegate.h
@@ -0,0 +1,107 @@
+// 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 COMPONENTS_EXO_WAYLAND_WAYLAND_KEYBOARD_DELEGATE_H_
+#define COMPONENTS_EXO_WAYLAND_WAYLAND_KEYBOARD_DELEGATE_H_
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "build/buildflag.h"
+#include "components/exo/keyboard_delegate.h"
+#include "components/exo/keyboard_observer.h"
+#include "components/exo/wayland/server_util.h"
+#include "components/exo/wayland/wayland_input_delegate.h"
+#include "ui/base/ui_features.h"
+#include "ui/events/keycodes/dom/keycode_converter.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/ime/ime_controller.h"
+#include "ash/shell.h"
+#include "ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.h"
+#endif
+
+#if BUILDFLAG(USE_XKBCOMMON)
+#include <xkbcommon/xkbcommon.h>
+#include "ui/events/keycodes/scoped_xkb.h"  // nogncheck
+#endif
+
+struct wl_client;
+struct wl_resource;
+
+namespace exo {
+namespace wayland {
+
+// Keyboard delegate class that accepts events for surfaces owned by the same
+// client as a keyboard resource.
+class WaylandKeyboardDelegate : public WaylandInputDelegate,
+                                public KeyboardDelegate,
+                                public KeyboardObserver
+#if defined(OS_CHROMEOS)
+    ,
+                                public ash::ImeController::Observer
+#endif
+{
+#if BUILDFLAG(USE_XKBCOMMON)
+ public:
+  explicit WaylandKeyboardDelegate(wl_resource* keyboard_resource);
+
+#if defined(OS_CHROMEOS)
+  ~WaylandKeyboardDelegate() override;
+#endif
+
+  // Overridden from KeyboardDelegate:
+  void OnKeyboardDestroying(Keyboard* keyboard) override;
+  bool CanAcceptKeyboardEventsForSurface(Surface* surface) const override;
+  void OnKeyboardEnter(
+      Surface* surface,
+      const base::flat_map<ui::DomCode, ui::DomCode>& pressed_keys) override;
+  void OnKeyboardLeave(Surface* surface) override;
+  uint32_t OnKeyboardKey(base::TimeTicks time_stamp,
+                         ui::DomCode key,
+                         bool pressed) override;
+  void OnKeyboardModifiers(int modifier_flags) override;
+
+#if defined(OS_CHROMEOS)
+  // Overridden from ImeController::Observer:
+  void OnCapsLockChanged(bool enabled) override;
+  void OnKeyboardLayoutNameChanged(const std::string& layout_name) override;
+#endif
+
+ private:
+  // Returns the corresponding key given a dom code.
+  uint32_t DomCodeToKey(ui::DomCode code) const;
+
+  // Returns a set of Xkb modififers given a set of modifier flags.
+  uint32_t ModifierFlagsToXkbModifiers(int modifier_flags);
+
+#if defined(OS_CHROMEOS)
+  // Send the named keyboard layout to the client.
+  void SendNamedLayout(const std::string& layout_name);
+#endif
+
+  // Send the keyboard layout named by XKB rules to the client.
+  void SendLayout(const xkb_rule_names* names);
+
+  // The client who own this keyboard instance.
+  wl_client* client() const;
+
+  // Returns the next serial to use for keyboard events.
+  uint32_t next_serial() const;
+
+  // The keyboard resource associated with the keyboard.
+  wl_resource* const keyboard_resource_;
+
+  // The Xkb state used for the keyboard.
+  std::unique_ptr<xkb_context, ui::XkbContextDeleter> xkb_context_;
+  std::unique_ptr<xkb_keymap, ui::XkbKeymapDeleter> xkb_keymap_;
+  std::unique_ptr<xkb_state, ui::XkbStateDeleter> xkb_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(WaylandKeyboardDelegate);
+#endif
+};
+
+}  // namespace wayland
+}  // namespace exo
+
+#endif  // COMPONENTS_EXO_WAYLAND_WAYLAND_KEYBOARD_DELEGATE_H_
diff --git a/components/exo/wayland/wayland_pointer_delegate.cc b/components/exo/wayland/wayland_pointer_delegate.cc
new file mode 100644
index 0000000..686ba9ec0
--- /dev/null
+++ b/components/exo/wayland/wayland_pointer_delegate.cc
@@ -0,0 +1,142 @@
+// 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 "components/exo/wayland/wayland_pointer_delegate.h"
+
+#include <linux/input.h>
+#include <wayland-server-core.h>
+#include <wayland-server-protocol-core.h>
+
+#include "components/exo/pointer.h"
+#include "components/exo/wayland/server_util.h"
+#include "ui/events/event_constants.h"
+
+namespace exo {
+namespace wayland {
+
+WaylandPointerDelegate::WaylandPointerDelegate(wl_resource* pointer_resource)
+    : pointer_resource_(pointer_resource) {}
+
+void WaylandPointerDelegate::OnPointerDestroying(Pointer* pointer) {
+  delete this;
+}
+
+bool WaylandPointerDelegate::CanAcceptPointerEventsForSurface(
+    Surface* surface) const {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  // We can accept events for this surface if the client is the same as the
+  // pointer.
+  return surface_resource &&
+         wl_resource_get_client(surface_resource) == client();
+}
+
+void WaylandPointerDelegate::OnPointerEnter(Surface* surface,
+                                            const gfx::PointF& location,
+                                            int button_flags) {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  DCHECK(surface_resource);
+  // Should we be sending button events to the client before the enter event
+  // if client's pressed button state is different from |button_flags|?
+  wl_pointer_send_enter(pointer_resource_, next_serial(), surface_resource,
+                        wl_fixed_from_double(location.x()),
+                        wl_fixed_from_double(location.y()));
+}
+
+void WaylandPointerDelegate::OnPointerLeave(Surface* surface) {
+  wl_resource* surface_resource = GetSurfaceResource(surface);
+  DCHECK(surface_resource);
+  wl_pointer_send_leave(pointer_resource_, next_serial(), surface_resource);
+}
+
+void WaylandPointerDelegate::OnPointerMotion(base::TimeTicks time_stamp,
+                                             const gfx::PointF& location) {
+  SendTimestamp(time_stamp);
+  wl_pointer_send_motion(pointer_resource_, TimeTicksToMilliseconds(time_stamp),
+                         wl_fixed_from_double(location.x()),
+                         wl_fixed_from_double(location.y()));
+}
+
+void WaylandPointerDelegate::OnPointerButton(base::TimeTicks time_stamp,
+                                             int button_flags,
+                                             bool pressed) {
+  struct {
+    ui::EventFlags flag;
+    uint32_t value;
+  } buttons[] = {
+      {ui::EF_LEFT_MOUSE_BUTTON, BTN_LEFT},
+      {ui::EF_RIGHT_MOUSE_BUTTON, BTN_RIGHT},
+      {ui::EF_MIDDLE_MOUSE_BUTTON, BTN_MIDDLE},
+      {ui::EF_FORWARD_MOUSE_BUTTON, BTN_FORWARD},
+      {ui::EF_BACK_MOUSE_BUTTON, BTN_BACK},
+  };
+  uint32_t serial = next_serial();
+  for (auto button : buttons) {
+    if (button_flags & button.flag) {
+      SendTimestamp(time_stamp);
+      wl_pointer_send_button(pointer_resource_, serial,
+                             TimeTicksToMilliseconds(time_stamp), button.value,
+                             pressed ? WL_POINTER_BUTTON_STATE_PRESSED
+                                     : WL_POINTER_BUTTON_STATE_RELEASED);
+    }
+  }
+}
+
+void WaylandPointerDelegate::OnPointerScroll(base::TimeTicks time_stamp,
+                                             const gfx::Vector2dF& offset,
+                                             bool discrete) {
+  // Same as Weston, the reference compositor.
+  const double kAxisStepDistance = 10.0 / ui::MouseWheelEvent::kWheelDelta;
+
+  if (wl_resource_get_version(pointer_resource_) >=
+      WL_POINTER_AXIS_SOURCE_SINCE_VERSION) {
+    int32_t axis_source =
+        discrete ? WL_POINTER_AXIS_SOURCE_WHEEL : WL_POINTER_AXIS_SOURCE_FINGER;
+    wl_pointer_send_axis_source(pointer_resource_, axis_source);
+  }
+
+  double x_value = offset.x() * kAxisStepDistance;
+  SendTimestamp(time_stamp);
+  wl_pointer_send_axis(pointer_resource_, TimeTicksToMilliseconds(time_stamp),
+                       WL_POINTER_AXIS_HORIZONTAL_SCROLL,
+                       wl_fixed_from_double(-x_value));
+
+  double y_value = offset.y() * kAxisStepDistance;
+  SendTimestamp(time_stamp);
+  wl_pointer_send_axis(pointer_resource_, TimeTicksToMilliseconds(time_stamp),
+                       WL_POINTER_AXIS_VERTICAL_SCROLL,
+                       wl_fixed_from_double(-y_value));
+}
+
+void WaylandPointerDelegate::OnPointerScrollStop(base::TimeTicks time_stamp) {
+  if (wl_resource_get_version(pointer_resource_) >=
+      WL_POINTER_AXIS_STOP_SINCE_VERSION) {
+    SendTimestamp(time_stamp);
+    wl_pointer_send_axis_stop(pointer_resource_,
+                              TimeTicksToMilliseconds(time_stamp),
+                              WL_POINTER_AXIS_HORIZONTAL_SCROLL);
+    SendTimestamp(time_stamp);
+    wl_pointer_send_axis_stop(pointer_resource_,
+                              TimeTicksToMilliseconds(time_stamp),
+                              WL_POINTER_AXIS_VERTICAL_SCROLL);
+  }
+}
+
+void WaylandPointerDelegate::OnPointerFrame() {
+  if (wl_resource_get_version(pointer_resource_) >=
+      WL_POINTER_FRAME_SINCE_VERSION) {
+    wl_pointer_send_frame(pointer_resource_);
+  }
+  wl_client_flush(client());
+}
+
+wl_client* WaylandPointerDelegate::client() const {
+  return wl_resource_get_client(pointer_resource_);
+}
+
+uint32_t WaylandPointerDelegate::next_serial() const {
+  return wl_display_next_serial(wl_client_get_display(client()));
+}
+
+}  // namespace wayland
+}  // namespace exo
diff --git a/components/exo/wayland/wayland_pointer_delegate.h b/components/exo/wayland/wayland_pointer_delegate.h
new file mode 100644
index 0000000..07d53a979
--- /dev/null
+++ b/components/exo/wayland/wayland_pointer_delegate.h
@@ -0,0 +1,58 @@
+// 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 COMPONENTS_EXO_WAYLAND_WAYLAND_POINTER_DELEGATE_H_
+#define COMPONENTS_EXO_WAYLAND_WAYLAND_POINTER_DELEGATE_H_
+
+#include "components/exo/pointer_delegate.h"
+#include "components/exo/wayland/wayland_input_delegate.h"
+
+struct wl_client;
+struct wl_resource;
+
+namespace exo {
+namespace wayland {
+
+// Pointer delegate class that accepts events for surfaces owned by the same
+// client as a pointer resource.
+class WaylandPointerDelegate : public WaylandInputDelegate,
+                               public PointerDelegate {
+ public:
+  explicit WaylandPointerDelegate(wl_resource* pointer_resource);
+
+  // Overridden from PointerDelegate:
+  void OnPointerDestroying(Pointer* pointer) override;
+  bool CanAcceptPointerEventsForSurface(Surface* surface) const override;
+  void OnPointerEnter(Surface* surface,
+                      const gfx::PointF& location,
+                      int button_flags) override;
+  void OnPointerLeave(Surface* surface) override;
+  void OnPointerMotion(base::TimeTicks time_stamp,
+                       const gfx::PointF& location) override;
+  void OnPointerButton(base::TimeTicks time_stamp,
+                       int button_flags,
+                       bool pressed) override;
+  void OnPointerScroll(base::TimeTicks time_stamp,
+                       const gfx::Vector2dF& offset,
+                       bool discrete) override;
+  void OnPointerScrollStop(base::TimeTicks time_stamp) override;
+  void OnPointerFrame() override;
+
+ private:
+  // The client who own this pointer instance.
+  wl_client* client() const;
+
+  // Returns the next serial to use for pointer events.
+  uint32_t next_serial() const;
+
+  // The pointer resource associated with the pointer.
+  wl_resource* const pointer_resource_;
+
+  DISALLOW_COPY_AND_ASSIGN(WaylandPointerDelegate);
+};
+
+}  // namespace wayland
+}  // namespace exo
+
+#endif  // COMPONENTS_EXO_WAYLAND_WAYLAND_POINTER_DELEGATE_H_
diff --git a/components/flags_ui/feature_entry.cc b/components/flags_ui/feature_entry.cc
index bfda221b..e1d703fc 100644
--- a/components/flags_ui/feature_entry.cc
+++ b/components/flags_ui/feature_entry.cc
@@ -6,16 +6,48 @@
 
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace flags_ui {
+namespace {
+
+// WARNING: '@' is also used in the html file. If you update this constant you
+// also need to update the html file.
+const char kMultiSeparatorChar = '@';
+
+}  // namespace
 
 const char kGenericExperimentChoiceDefault[] = "Default";
 const char kGenericExperimentChoiceEnabled[] = "Enabled";
 const char kGenericExperimentChoiceDisabled[] = "Disabled";
 const char kGenericExperimentChoiceAutomatic[] = "Automatic";
 
+bool FeatureEntry::InternalNameMatches(const std::string& name) const {
+  if (!base::StartsWith(name, internal_name, base::CompareCase::SENSITIVE))
+    return false;
+
+  const size_t internal_name_length = strlen(internal_name);
+  switch (type) {
+    case FeatureEntry::SINGLE_VALUE:
+    case FeatureEntry::SINGLE_DISABLE_VALUE:
+    case FeatureEntry::ORIGIN_LIST_VALUE:
+      return name.size() == internal_name_length;
+
+    case FeatureEntry::MULTI_VALUE:
+    case FeatureEntry::ENABLE_DISABLE_VALUE:
+    case FeatureEntry::FEATURE_VALUE:
+    case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
+      // Check that the pattern matches what's produced by NameForOption().
+      int index = -1;
+      return name.size() > internal_name_length + 1 &&
+             name[internal_name_length] == kMultiSeparatorChar &&
+             base::StringToInt(name.substr(internal_name_length + 1), &index) &&
+             index >= 0 && index < num_options;
+  }
+}
+
 std::string FeatureEntry::NameForOption(int index) const {
   DCHECK(type == FeatureEntry::MULTI_VALUE ||
          type == FeatureEntry::ENABLE_DISABLE_VALUE ||
@@ -100,9 +132,7 @@
 
 namespace testing {
 
-// WARNING: '@' is also used in the html file. If you update this constant you
-// also need to update the html file.
-const char kMultiSeparator[] = "@";
+const char kMultiSeparator[] = {kMultiSeparatorChar, '\0'};
 
 }  // namespace testing
 
diff --git a/components/flags_ui/feature_entry.h b/components/flags_ui/feature_entry.h
index 15a2610..b93f1fb 100644
--- a/components/flags_ui/feature_entry.h
+++ b/components/flags_ui/feature_entry.h
@@ -179,6 +179,11 @@
   // FEATURE_WITH_VARIATIONS_VALUE.
   const char* feature_trial_name;
 
+  // Check whether internal |name| matches this FeatureEntry. Depending on the
+  // type of entry, this compared it to either |internal_name| or the values
+  // produced by NameForOption().
+  bool InternalNameMatches(const std::string& name) const;
+
   // Returns the name used in prefs for the option at the specified |index|.
   // Only used for types that use |num_options|.
   std::string NameForOption(int index) const;
diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_state.cc
index 774b38b..fa27d5a5 100644
--- a/components/flags_ui/flags_state.cc
+++ b/components/flags_ui/flags_state.cc
@@ -114,24 +114,6 @@
   }
 }
 
-// Adds the internal names for the specified entry to |names|.
-void AddInternalName(const FeatureEntry& e, std::set<std::string>* names) {
-  switch (e.type) {
-    case FeatureEntry::SINGLE_VALUE:
-    case FeatureEntry::SINGLE_DISABLE_VALUE:
-    case FeatureEntry::ORIGIN_LIST_VALUE:
-      names->insert(e.internal_name);
-      break;
-    case FeatureEntry::MULTI_VALUE:
-    case FeatureEntry::ENABLE_DISABLE_VALUE:
-    case FeatureEntry::FEATURE_VALUE:
-    case FeatureEntry::FEATURE_WITH_PARAMS_VALUE:
-      for (int i = 0; i < e.num_options; ++i)
-        names->insert(e.NameForOption(i));
-      break;
-  }
-}
-
 // Confirms that an entry is valid, used in a DCHECK in
 // SanitizeList below.
 bool ValidateFeatureEntry(const FeatureEntry& e) {
@@ -782,54 +764,54 @@
     command_line->AppendSwitchASCII(switch_name, switch_value);
 }
 
-void FlagsState::SanitizeList(FlagsStorage* flags_storage) const {
-  std::set<std::string> known_entries;
-  for (size_t i = 0; i < num_feature_entries_; ++i) {
-    DCHECK(ValidateFeatureEntry(feature_entries_[i]));
-    AddInternalName(feature_entries_[i], &known_entries);
+std::set<std::string> FlagsState::SanitizeList(
+    const std::set<std::string>& enabled_entries,
+    int platform_mask) const {
+  std::set<std::string> new_enabled_entries;
+
+  // For each entry in |enabled_entries|, check whether it exists in the list
+  // of supported features. Remove those that don't. Note: Even though this is
+  // an O(n^2) search, this is more efficient than creating a set from
+  // |feature_entries_| first because |feature_entries_| is large and
+  // |enabled_entries| should generally be small/empty.
+  const FeatureEntry* features_end = feature_entries_ + num_feature_entries_;
+  for (const std::string& entry_name : enabled_entries) {
+    if (features_end !=
+        std::find_if(feature_entries_, features_end,
+                     [entry_name, platform_mask](const FeatureEntry& e) {
+                       DCHECK(ValidateFeatureEntry(e));
+                       return (e.supported_platforms & platform_mask) &&
+                              e.InternalNameMatches(entry_name);
+                     })) {
+      new_enabled_entries.insert(entry_name);
+    }
   }
 
-  std::set<std::string> enabled_entries = flags_storage->GetFlags();
-
-  std::set<std::string> new_enabled_entries =
-      base::STLSetIntersection<std::set<std::string>>(known_entries,
-                                                      enabled_entries);
-
-  if (new_enabled_entries != enabled_entries)
-    flags_storage->SetFlags(new_enabled_entries);
+  return new_enabled_entries;
 }
 
 void FlagsState::GetSanitizedEnabledFlags(FlagsStorage* flags_storage,
                                           std::set<std::string>* result) const {
-  SanitizeList(flags_storage);
-  *result = flags_storage->GetFlags();
+  std::set<std::string> enabled_entries = flags_storage->GetFlags();
+  std::set<std::string> new_enabled_entries = SanitizeList(enabled_entries, -1);
+  if (new_enabled_entries.size() != enabled_entries.size())
+    flags_storage->SetFlags(new_enabled_entries);
+  result->swap(new_enabled_entries);
 }
 
 void FlagsState::GetSanitizedEnabledFlagsForCurrentPlatform(
     FlagsStorage* flags_storage,
     std::set<std::string>* result) const {
+  // TODO(asvitkine): Consider making GetSanitizedEnabledFlags() do the platform
+  // filtering by default so that we don't need two calls to SanitizeList().
   GetSanitizedEnabledFlags(flags_storage, result);
 
-  // Filter out any entries that aren't enabled on the current platform.  We
-  // don't remove these from prefs else syncing to a platform with a different
-  // set of entries would be lossy.
-  std::set<std::string> platform_entries;
-  int current_platform = GetCurrentPlatform();
-  for (size_t i = 0; i < num_feature_entries_; ++i) {
-    const FeatureEntry& entry = feature_entries_[i];
-    if (entry.supported_platforms & current_platform)
-      AddInternalName(entry, &platform_entries);
+  int platform_mask = GetCurrentPlatform();
 #if defined(OS_CHROMEOS)
-    if (feature_entries_[i].supported_platforms & kOsCrOSOwnerOnly)
-      AddInternalName(entry, &platform_entries);
+  platform_mask |= kOsCrOSOwnerOnly;
 #endif
-  }
-
-  std::set<std::string> new_enabled_entries =
-      base::STLSetIntersection<std::set<std::string>>(platform_entries,
-                                                      *result);
-
-  result->swap(new_enabled_entries);
+  std::set<std::string> platform_entries = SanitizeList(*result, platform_mask);
+  result->swap(platform_entries);
 }
 
 void FlagsState::GenerateFlagsToSwitchesMapping(
diff --git a/components/flags_ui/flags_state.h b/components/flags_ui/flags_state.h
index 21ad59df5..6ce8321 100644
--- a/components/flags_ui/flags_state.h
+++ b/components/flags_ui/flags_state.h
@@ -176,10 +176,15 @@
       bool feature_state,
       base::CommandLine* command_line);
 
-  // Removes all entries from prefs::kEnabledLabsExperiments that are unknown,
-  // to prevent this list to become very long as entries are added and removed.
-  void SanitizeList(FlagsStorage* flags_storage) const;
+  // Sanitizes |enabled_entries| to only contain entries that are defined in the
+  // |feature_entries_| and whose |supported_platforms| matches |platform_mask|.
+  // Pass -1 to |platform_mask| to not do platform filtering.
+  std::set<std::string> SanitizeList(
+      const std::set<std::string>& enabled_entries,
+      int platform_mask) const;
 
+  // Gets sanitized entries from |flags_storage|, filtering out any entries that
+  // don't exist in |feature_entries_|, and updates |flags_storage|.
   void GetSanitizedEnabledFlags(FlagsStorage* flags_storage,
                                 std::set<std::string>* result) const;
 
diff --git a/components/metrics/legacy_call_stack_profile_builder.cc b/components/metrics/legacy_call_stack_profile_builder.cc
index c5b97a2..6865fc8 100644
--- a/components/metrics/legacy_call_stack_profile_builder.cc
+++ b/components/metrics/legacy_call_stack_profile_builder.cc
@@ -162,7 +162,8 @@
 LegacyCallStackProfileBuilder::LegacyCallStackProfileBuilder(
     const CallStackProfileParams& profile_params,
     base::OnceClosure completed_callback)
-    : profile_params_(profile_params),
+    : process_milestones_(0),
+      profile_params_(profile_params),
       profile_start_time_(base::TimeTicks::Now()) {
   completed_callback_ = std::move(completed_callback);
 }
@@ -173,8 +174,7 @@
   // The code inside this method must not do anything that could acquire a
   // mutex, including allocating memory (which includes LOG messages) because
   // that mutex could be held by a stopped thread, thus resulting in deadlock.
-  sample_.process_milestones =
-      base::subtle::NoBarrier_Load(&g_process_milestones);
+  process_milestones_ = base::subtle::NoBarrier_Load(&g_process_milestones);
 }
 
 void LegacyCallStackProfileBuilder::OnSampleCompleted(
@@ -185,12 +185,15 @@
 void LegacyCallStackProfileBuilder::OnSampleCompleted(
     std::vector<base::StackSamplingProfiler::Frame> frames,
     size_t count) {
+  Sample sample;
+  sample.process_milestones = process_milestones_;
+
   // Assemble sample_ from |frames| first.
   for (const auto& frame : frames) {
     const base::ModuleCache::Module& module(frame.module);
     if (!module.is_valid) {
-      sample_.frames.emplace_back(frame.instruction_pointer,
-                                  kUnknownModuleIndex);
+      sample.frames.emplace_back(frame.instruction_pointer,
+                                 kUnknownModuleIndex);
       continue;
     }
 
@@ -202,12 +205,12 @@
       loc = module_index_.insert(std::make_pair(module.base_address, index))
                 .first;
     }
-    sample_.frames.emplace_back(frame.instruction_pointer, loc->second);
+    sample.frames.emplace_back(frame.instruction_pointer, loc->second);
   }
 
-  // Write CallStackProfile::Sample protocol buffer message based on sample_.
+  // Write CallStackProfile::Sample protocol buffer message based on sample.
   int existing_sample_index = -1;
-  auto location = sample_index_.find(sample_);
+  auto location = sample_index_.find(sample);
   if (location != sample_index_.end())
     existing_sample_index = location->second;
 
@@ -220,17 +223,15 @@
 
   CallStackProfile::Sample* sample_proto =
       proto_profile_.add_deprecated_sample();
-  CopySampleToProto(sample_, modules_, sample_proto);
+  CopySampleToProto(sample, modules_, sample_proto);
   sample_proto->set_count(count);
-  CopyAnnotationsToProto(sample_.process_milestones & ~milestones_,
+  CopyAnnotationsToProto(sample.process_milestones & ~milestones_,
                          sample_proto);
-  milestones_ = sample_.process_milestones;
+  milestones_ = sample.process_milestones;
 
   sample_index_.insert(std::make_pair(
-      std::move(sample_),
+      std::move(sample),
       static_cast<int>(proto_profile_.deprecated_sample_size()) - 1));
-
-  sample_ = Sample();
 }
 
 // Build a SampledProfile in the protocol buffer message format from the
diff --git a/components/metrics/legacy_call_stack_profile_builder.h b/components/metrics/legacy_call_stack_profile_builder.h
index ef0dc3e..d8dee43 100644
--- a/components/metrics/legacy_call_stack_profile_builder.h
+++ b/components/metrics/legacy_call_stack_profile_builder.h
@@ -132,8 +132,8 @@
   // The collected stack samples in proto buffer message format.
   CallStackProfile proto_profile_;
 
-  // The current sample being recorded.
-  Sample sample_;
+  // The process milestones to use for the next sample.
+  uint32_t process_milestones_;
 
   // The indexes of samples, indexed by the sample.
   std::map<Sample, int> sample_index_;
diff --git a/components/metrics/legacy_call_stack_profile_builder_unittest.cc b/components/metrics/legacy_call_stack_profile_builder_unittest.cc
index d47cbc8..2415693 100644
--- a/components/metrics/legacy_call_stack_profile_builder_unittest.cc
+++ b/components/metrics/legacy_call_stack_profile_builder_unittest.cc
@@ -218,6 +218,7 @@
   ASSERT_TRUE(proto.has_call_stack_profile());
   ASSERT_EQ(1, proto.call_stack_profile().deprecated_sample_size());
   ASSERT_EQ(43, proto.call_stack_profile().deprecated_sample(0).count());
+  EXPECT_EQ(2, proto.call_stack_profile().deprecated_sample(0).frame_size());
 }
 
 TEST(LegacyCallStackProfileBuilderTest, SamplesNotDeduped) {
@@ -263,6 +264,65 @@
 
   ASSERT_TRUE(proto.has_call_stack_profile());
   ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size());
+  EXPECT_EQ(2, proto.call_stack_profile().deprecated_sample(0).frame_size());
+  EXPECT_EQ(2, proto.call_stack_profile().deprecated_sample(1).frame_size());
+}
+
+TEST(LegacyCallStackProfileBuilderTest, SamplesDedupedAndNotDeduped) {
+  auto profile_builder =
+      std::make_unique<TestingLegacyCallStackProfileBuilder>(kProfileParams);
+
+#if defined(OS_WIN)
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+
+  const uintptr_t module_base_address1 = 0x1000;
+  Module module1 = {module_base_address1, "1", module_path};
+  Frame frame1 = {module_base_address1 + 0x10, module1};
+
+  const uintptr_t module_base_address2 = 0x1100;
+  Module module2 = {module_base_address2, "2", module_path};
+  Frame frame2 = {module_base_address2 + 0x20, module2};
+
+  std::vector<Frame> frames1 = {frame1, frame2};
+  std::vector<Frame> frames2 = {frame2, frame1};
+
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames1, 42);
+
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames1);
+
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames2);
+
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  EXPECT_TRUE(proto.has_process());
+  EXPECT_EQ(BROWSER_PROCESS, proto.process());
+  EXPECT_TRUE(proto.has_thread());
+  EXPECT_EQ(MAIN_THREAD, proto.thread());
+  EXPECT_TRUE(proto.has_trigger_event());
+  EXPECT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
+
+  EXPECT_TRUE(proto.has_call_stack_profile());
+  ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size());
+  EXPECT_EQ(43, proto.call_stack_profile().deprecated_sample(0).count());
+  ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample(0).frame_size());
+  EXPECT_EQ(0x10u,
+            proto.call_stack_profile().deprecated_sample(0).frame(0).address());
+  EXPECT_EQ(0x20u,
+            proto.call_stack_profile().deprecated_sample(0).frame(1).address());
+  ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample(1).frame_size());
+  EXPECT_EQ(1, proto.call_stack_profile().deprecated_sample(1).count());
+  EXPECT_EQ(0x20u,
+            proto.call_stack_profile().deprecated_sample(1).frame(0).address());
+  EXPECT_EQ(0x10u,
+            proto.call_stack_profile().deprecated_sample(1).frame(1).address());
 }
 
 TEST(LegacyCallStackProfileBuilderTest, Modules) {
diff --git a/components/offline_pages/core/background/request_coordinator_stub_taco.cc b/components/offline_pages/core/background/request_coordinator_stub_taco.cc
index bfda496..82d545fe 100644
--- a/components/offline_pages/core/background/request_coordinator_stub_taco.cc
+++ b/components/offline_pages/core/background/request_coordinator_stub_taco.cc
@@ -88,14 +88,40 @@
 }
 
 void RequestCoordinatorStubTaco::CreateRequestCoordinator() {
-  request_coordinator_ = std::make_unique<RequestCoordinator>(
+  CHECK(!request_coordinator_)
+      << "CreateRequestCoordinator can be called only once";
+  owned_request_coordinator_ = std::make_unique<RequestCoordinator>(
       std::move(policy_), std::move(offliner_), std::move(queue_),
       std::move(scheduler_), network_quality_tracker_.get(),
       std::move(ukm_reporter_), std::move(active_tab_info_));
+  request_coordinator_ = owned_request_coordinator_.get();
 }
 
 RequestCoordinator* RequestCoordinatorStubTaco::request_coordinator() {
   CHECK(request_coordinator_);
-  return request_coordinator_.get();
+  return request_coordinator_;
 }
+
+base::RepeatingCallback<std::unique_ptr<KeyedService>(content::BrowserContext*)>
+RequestCoordinatorStubTaco::FactoryFunction() {
+  return base::BindRepeating(
+      &RequestCoordinatorStubTaco::InternalFactoryFunction, GetWeakPtr());
+}
+
+// static
+std::unique_ptr<KeyedService>
+RequestCoordinatorStubTaco::InternalFactoryFunction(
+    base::WeakPtr<RequestCoordinatorStubTaco> taco,
+    content::BrowserContext* context) {
+  if (!taco)
+    return nullptr;
+  // Call CreateRequestCoordinator if it hasn't already been called.
+  if (!taco->request_coordinator_) {
+    taco->CreateRequestCoordinator();
+  }
+  // This function can only be used once.
+  CHECK(taco->owned_request_coordinator_);
+  return std::move(taco->owned_request_coordinator_);
+}
+
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/background/request_coordinator_stub_taco.h b/components/offline_pages/core/background/request_coordinator_stub_taco.h
index 2ffe37e..d1c0756 100644
--- a/components/offline_pages/core/background/request_coordinator_stub_taco.h
+++ b/components/offline_pages/core/background/request_coordinator_stub_taco.h
@@ -14,6 +14,9 @@
 #include "components/offline_pages/core/background/request_queue_store.h"
 #include "components/offline_pages/core/background/scheduler.h"
 
+namespace content {
+class BrowserContext;
+}
 namespace network {
 class NetworkQualityTracker;
 }
@@ -57,7 +60,21 @@
 
   RequestCoordinator* request_coordinator();
 
+  // A factory function that can be used with
+  // RequestCoordinatorFactory::SetTestingFactoryAndUse.
+  base::RepeatingCallback<
+      std::unique_ptr<KeyedService>(content::BrowserContext*)>
+  FactoryFunction();
+
  private:
+  base::WeakPtr<RequestCoordinatorStubTaco> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+  static std::unique_ptr<KeyedService> InternalFactoryFunction(
+      base::WeakPtr<RequestCoordinatorStubTaco> taco,
+      content::BrowserContext*);
+
   bool store_overridden_ = false;
   bool queue_overridden_ = false;
 
@@ -69,7 +86,12 @@
   std::unique_ptr<OfflinePagesUkmReporter> ukm_reporter_;
   std::unique_ptr<RequestCoordinator::ActiveTabInfo> active_tab_info_;
 
-  std::unique_ptr<RequestCoordinator> request_coordinator_;
+  // This is null if the request coordinator was given to the
+  // RequestCoordinatorFactory through the factory function.
+  std::unique_ptr<RequestCoordinator> owned_request_coordinator_;
+  RequestCoordinator* request_coordinator_ = nullptr;
+
+  base::WeakPtrFactory<RequestCoordinatorStubTaco> weak_ptr_factory_{this};
 };
 }  // namespace offline_pages
 #endif  // COMPONENTS_OFFLINE_PAGES_CORE_BACKGROUND_REQUEST_COORDINATOR_STUB_TACO_H_
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 2d750b2..50a47a9 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -204,10 +204,8 @@
 
 #if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
 // static
-const gfx::VectorIcon& AutocompleteMatch::TypeToVectorIcon(
-    Type type,
-    bool is_bookmark,
-    DocumentType document_type) {
+const gfx::VectorIcon& AutocompleteMatch::TypeToVectorIcon(Type type,
+                                                           bool is_bookmark) {
   if (is_bookmark)
     return omnibox::kBookmarkIcon;
 
@@ -224,23 +222,8 @@
     case Type::PHYSICAL_WEB_DEPRECATED:
     case Type::PHYSICAL_WEB_OVERFLOW_DEPRECATED:
     case Type::TAB_SEARCH_DEPRECATED:
-      return omnibox::kPageIcon;
-
     case Type::DOCUMENT_SUGGESTION:
-      switch (document_type) {
-        case DocumentType::DRIVE_DOCS:
-          return omnibox::kDriveDocsIcon;
-        case DocumentType::DRIVE_FORMS:
-          return omnibox::kDriveFormsIcon;
-        case DocumentType::DRIVE_SHEETS:
-          return omnibox::kDriveSheetsIcon;
-        case DocumentType::DRIVE_SLIDES:
-          return omnibox::kDriveSlidesIcon;
-        case DocumentType::DRIVE_OTHER:
-          return omnibox::kDriveLogoIcon;
-        default:
-          return omnibox::kPageIcon;
-      }
+      return omnibox::kPageIcon;
 
     case Type::SEARCH_WHAT_YOU_TYPED:
     case Type::SEARCH_HISTORY:
@@ -272,6 +255,33 @@
   static const gfx::VectorIcon dummy = {};
   return dummy;
 }
+
+const gfx::VectorIcon& AutocompleteMatch::GetVectorIcon(
+    bool is_bookmark) const {
+  if (is_bookmark)
+    return omnibox::kBookmarkIcon;
+  switch (type) {
+    case Type::DOCUMENT_SUGGESTION:
+      switch (document_type) {
+        case DocumentType::DRIVE_DOCS:
+          return omnibox::kDriveDocsIcon;
+        case DocumentType::DRIVE_FORMS:
+          return omnibox::kDriveFormsIcon;
+        case DocumentType::DRIVE_SHEETS:
+          return omnibox::kDriveSheetsIcon;
+        case DocumentType::DRIVE_SLIDES:
+          return omnibox::kDriveSlidesIcon;
+        case DocumentType::DRIVE_OTHER:
+          return omnibox::kDriveLogoIcon;
+        default:
+          return omnibox::kPageIcon;
+      }
+    case Type::PEDAL:
+      return (pedal ? pedal->GetVectorIcon() : omnibox::kPedalIcon);
+    default:
+      return TypeToVectorIcon(type, is_bookmark);
+  }
+}
 #endif
 
 // static
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index f1e97722..3020d87d 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -139,9 +139,12 @@
   // Gets the vector icon identifier for the icon to be shown for |type|. If
   // |is_bookmark| is true, returns a bookmark icon rather than what the type
   // would determine.
-  static const gfx::VectorIcon& TypeToVectorIcon(Type type,
-                                                 bool is_bookmark,
-                                                 DocumentType document_type);
+  static const gfx::VectorIcon& TypeToVectorIcon(Type type, bool is_bookmark);
+
+  // Gets the VectorIcon to be shown for a match, which may depend on match
+  // contents.  This is preferable to using TypeToVectorIcon when a match
+  // instance is available because it can make a more informed choice.
+  const gfx::VectorIcon& GetVectorIcon(bool is_bookmark) const;
 #endif
 
   // Comparison function for determining when one match is better than another.
diff --git a/components/omnibox/browser/omnibox_pedal.cc b/components/omnibox/browser/omnibox_pedal.cc
index 079d5e9..176eac4 100644
--- a/components/omnibox/browser/omnibox_pedal.cc
+++ b/components/omnibox/browser/omnibox_pedal.cc
@@ -10,6 +10,10 @@
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+#include "components/omnibox/browser/vector_icons.h"  // nogncheck
+#endif
+
 OmniboxPedal::LabelStrings::LabelStrings(int id_hint,
                                          int id_hint_short,
                                          int id_suggestion_contents)
@@ -56,6 +60,12 @@
   return true;
 }
 
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+const gfx::VectorIcon& OmniboxPedal::GetVectorIcon() const {
+  return omnibox::kPedalIcon;
+}
+#endif
+
 bool OmniboxPedal::IsTriggerMatch(const base::string16& match_text) const {
   return triggers_.find(match_text) != triggers_.end();
 }
diff --git a/components/omnibox/browser/omnibox_pedal.h b/components/omnibox/browser/omnibox_pedal.h
index d7d1e81..38db74c2 100644
--- a/components/omnibox/browser/omnibox_pedal.h
+++ b/components/omnibox/browser/omnibox_pedal.h
@@ -9,8 +9,16 @@
 
 #include "base/strings/string16.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/omnibox/browser/buildflags.h"
 #include "url/gurl.h"
 
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+namespace gfx {
+struct VectorIcon;
+}
+#endif
+
 class AutocompleteProviderClient;
 class OmniboxEditController;
 class OmniboxClient;
@@ -86,6 +94,11 @@
   // Pedal may not be ready to trigger if no update is available.)
   virtual bool IsReadyToTrigger(const AutocompleteProviderClient& client) const;
 
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+  // Returns the vector icon to represent this Pedal's action in suggestion.
+  virtual const gfx::VectorIcon& GetVectorIcon() const;
+#endif
+
   // Returns true if the preprocessed match suggestion text triggers
   // presentation of this Pedal.  This is not intended for general use,
   // and only OmniboxPedalProvider should need to call this method.
diff --git a/components/omnibox/browser/omnibox_pedal_implementations.cc b/components/omnibox/browser/omnibox_pedal_implementations.cc
index 6440a7f..17e00c0b 100644
--- a/components/omnibox/browser/omnibox_pedal_implementations.cc
+++ b/components/omnibox/browser/omnibox_pedal_implementations.cc
@@ -5,11 +5,17 @@
 #include "components/omnibox/browser/omnibox_pedal_implementations.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
+#include "components/omnibox/browser/buildflags.h"
 #include "components/omnibox/browser/omnibox_client.h"
 #include "components/omnibox/browser/omnibox_pedal.h"
 #include "components/strings/grit/components_strings.h"
 
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+#include "components/omnibox/browser/vector_icons.h"  // nogncheck
+#endif
+
 // A small convenience wrapper for the common implementation pattern below.
 class OmniboxPedalCommon : public OmniboxPedal {
  public:
@@ -56,6 +62,12 @@
                 "history clear",
                 "history clear chrome",
             }) {}
+
+#if (!defined(OS_ANDROID) || BUILDFLAG(ENABLE_VR)) && !defined(OS_IOS)
+  const gfx::VectorIcon& GetVectorIcon() const override {
+    return omnibox::kAnswerWhenIsIcon;
+  }
+#endif
 };
 
 // =============================================================================
diff --git a/components/omnibox/browser/omnibox_popup_model.cc b/components/omnibox/browser/omnibox_popup_model.cc
index cd710d1..f7321dc1 100644
--- a/components/omnibox/browser/omnibox_popup_model.cc
+++ b/components/omnibox/browser/omnibox_popup_model.cc
@@ -323,8 +323,7 @@
       return edit_model_->client()->GetSizedIcon(favicon);
   }
 
-  const auto& vector_icon_type = AutocompleteMatch::TypeToVectorIcon(
-      match.type, IsStarredMatch(match), match.document_type);
+  const auto& vector_icon_type = match.GetVectorIcon(IsStarredMatch(match));
 
   return edit_model_->client()->GetSizedIcon(vector_icon_type,
                                              vector_icon_color);
diff --git a/components/omnibox/browser/omnibox_view.cc b/components/omnibox/browser/omnibox_view.cc
index 35622cc1..bdf248f 100644
--- a/components/omnibox/browser/omnibox_view.cc
+++ b/components/omnibox/browser/omnibox_view.cc
@@ -134,8 +134,7 @@
   // For tests, model_ will be null.
   if (!model_) {
     const gfx::VectorIcon& vector_icon = AutocompleteMatch::TypeToVectorIcon(
-        AutocompleteMatchType::URL_WHAT_YOU_TYPED, false /*is_bookmark*/,
-        AutocompleteMatch::DocumentType::NONE);
+        AutocompleteMatchType::URL_WHAT_YOU_TYPED, /*is_bookmark=*/false);
     return gfx::CreateVectorIcon(vector_icon, dip_size, color);
   }
 
@@ -165,8 +164,7 @@
   const bool is_bookmarked =
       bookmark_model && bookmark_model->IsBookmarked(match.destination_url);
 
-  const gfx::VectorIcon& vector_icon = AutocompleteMatch::TypeToVectorIcon(
-      match.type, is_bookmarked, match.document_type);
+  const gfx::VectorIcon& vector_icon = match.GetVectorIcon(is_bookmarked);
   return gfx::CreateVectorIcon(vector_icon, dip_size, color);
 #endif  // defined(OS_ANDROID) || defined(OS_IOS)
 }
diff --git a/components/resources/default_100_percent/autofill/amex.png b/components/resources/default_100_percent/autofill/amex.png
index d012783..e999a3d 100644
--- a/components/resources/default_100_percent/autofill/amex.png
+++ b/components/resources/default_100_percent/autofill/amex.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/cc-generic.png b/components/resources/default_100_percent/autofill/cc-generic.png
index 68ceaf6..8fab33c 100644
--- a/components/resources/default_100_percent/autofill/cc-generic.png
+++ b/components/resources/default_100_percent/autofill/cc-generic.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/credit_card_cvc_hint_amex.png b/components/resources/default_100_percent/autofill/credit_card_cvc_hint_amex.png
index a6df7c5..60f2007e 100644
--- a/components/resources/default_100_percent/autofill/credit_card_cvc_hint_amex.png
+++ b/components/resources/default_100_percent/autofill/credit_card_cvc_hint_amex.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/diners.png b/components/resources/default_100_percent/autofill/diners.png
index 3bd2ec4..60749a1f 100644
--- a/components/resources/default_100_percent/autofill/diners.png
+++ b/components/resources/default_100_percent/autofill/diners.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/discover.png b/components/resources/default_100_percent/autofill/discover.png
index 9ec9baab..39e98a7 100644
--- a/components/resources/default_100_percent/autofill/discover.png
+++ b/components/resources/default_100_percent/autofill/discover.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/googlepay.png b/components/resources/default_100_percent/autofill/googlepay.png
index 4be53902..51e11331 100644
--- a/components/resources/default_100_percent/autofill/googlepay.png
+++ b/components/resources/default_100_percent/autofill/googlepay.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/infobar_autofill_cc.png b/components/resources/default_100_percent/autofill/infobar_autofill_cc.png
index b733627..0bf8335 100644
--- a/components/resources/default_100_percent/autofill/infobar_autofill_cc.png
+++ b/components/resources/default_100_percent/autofill/infobar_autofill_cc.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/infobar_autofill_googlepay_with_divider.png b/components/resources/default_100_percent/autofill/infobar_autofill_googlepay_with_divider.png
index 3aed9da..c2fd403 100644
--- a/components/resources/default_100_percent/autofill/infobar_autofill_googlepay_with_divider.png
+++ b/components/resources/default_100_percent/autofill/infobar_autofill_googlepay_with_divider.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/mastercard.png b/components/resources/default_100_percent/autofill/mastercard.png
index bac5d8c..6672bbf 100644
--- a/components/resources/default_100_percent/autofill/mastercard.png
+++ b/components/resources/default_100_percent/autofill/mastercard.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/migration_header.png b/components/resources/default_100_percent/autofill/migration_header.png
index 3500dc24..f72197d7 100644
--- a/components/resources/default_100_percent/autofill/migration_header.png
+++ b/components/resources/default_100_percent/autofill/migration_header.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/mir.png b/components/resources/default_100_percent/autofill/mir.png
index 2e094f4..eeb329917 100644
--- a/components/resources/default_100_percent/autofill/mir.png
+++ b/components/resources/default_100_percent/autofill/mir.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/unionpay.png b/components/resources/default_100_percent/autofill/unionpay.png
index e0ab343c..e3305c3f 100644
--- a/components/resources/default_100_percent/autofill/unionpay.png
+++ b/components/resources/default_100_percent/autofill/unionpay.png
Binary files differ
diff --git a/components/resources/default_100_percent/autofill/visa.png b/components/resources/default_100_percent/autofill/visa.png
index c7a9d8ee..4191fde 100644
--- a/components/resources/default_100_percent/autofill/visa.png
+++ b/components/resources/default_100_percent/autofill/visa.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/amex.png b/components/resources/default_200_percent/autofill/amex.png
index a9749749..985c8dc 100644
--- a/components/resources/default_200_percent/autofill/amex.png
+++ b/components/resources/default_200_percent/autofill/amex.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/cc-generic.png b/components/resources/default_200_percent/autofill/cc-generic.png
index 5eab89e..83f5f76 100644
--- a/components/resources/default_200_percent/autofill/cc-generic.png
+++ b/components/resources/default_200_percent/autofill/cc-generic.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/credit_card_cvc_hint.png b/components/resources/default_200_percent/autofill/credit_card_cvc_hint.png
index 004e9c4b..1e17dfd 100644
--- a/components/resources/default_200_percent/autofill/credit_card_cvc_hint.png
+++ b/components/resources/default_200_percent/autofill/credit_card_cvc_hint.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/credit_card_cvc_hint_amex.png b/components/resources/default_200_percent/autofill/credit_card_cvc_hint_amex.png
index d65dc10..3c28af8 100644
--- a/components/resources/default_200_percent/autofill/credit_card_cvc_hint_amex.png
+++ b/components/resources/default_200_percent/autofill/credit_card_cvc_hint_amex.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/diners.png b/components/resources/default_200_percent/autofill/diners.png
index 522f0104c..79478797 100644
--- a/components/resources/default_200_percent/autofill/diners.png
+++ b/components/resources/default_200_percent/autofill/diners.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/elo.png b/components/resources/default_200_percent/autofill/elo.png
index ba88605..07f5fb6 100644
--- a/components/resources/default_200_percent/autofill/elo.png
+++ b/components/resources/default_200_percent/autofill/elo.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/googlepay.png b/components/resources/default_200_percent/autofill/googlepay.png
index 1a2c438..5cb11e8 100644
--- a/components/resources/default_200_percent/autofill/googlepay.png
+++ b/components/resources/default_200_percent/autofill/googlepay.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/infobar_autofill_cc.png b/components/resources/default_200_percent/autofill/infobar_autofill_cc.png
index 6cdb9ce6..18391c4 100644
--- a/components/resources/default_200_percent/autofill/infobar_autofill_cc.png
+++ b/components/resources/default_200_percent/autofill/infobar_autofill_cc.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/infobar_autofill_googlepay_with_divider.png b/components/resources/default_200_percent/autofill/infobar_autofill_googlepay_with_divider.png
index 067ceee..23b408d 100644
--- a/components/resources/default_200_percent/autofill/infobar_autofill_googlepay_with_divider.png
+++ b/components/resources/default_200_percent/autofill/infobar_autofill_googlepay_with_divider.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/jcb.png b/components/resources/default_200_percent/autofill/jcb.png
index 381c9389..f94e62e 100644
--- a/components/resources/default_200_percent/autofill/jcb.png
+++ b/components/resources/default_200_percent/autofill/jcb.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/mastercard.png b/components/resources/default_200_percent/autofill/mastercard.png
index e496711a..9ab8bea4 100644
--- a/components/resources/default_200_percent/autofill/mastercard.png
+++ b/components/resources/default_200_percent/autofill/mastercard.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/migration_header.png b/components/resources/default_200_percent/autofill/migration_header.png
index 23ad3369..1749cb5 100644
--- a/components/resources/default_200_percent/autofill/migration_header.png
+++ b/components/resources/default_200_percent/autofill/migration_header.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/mir.png b/components/resources/default_200_percent/autofill/mir.png
index 02fc43c..2092b4d 100644
--- a/components/resources/default_200_percent/autofill/mir.png
+++ b/components/resources/default_200_percent/autofill/mir.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/unionpay.png b/components/resources/default_200_percent/autofill/unionpay.png
index 78380604..5c8d633 100644
--- a/components/resources/default_200_percent/autofill/unionpay.png
+++ b/components/resources/default_200_percent/autofill/unionpay.png
Binary files differ
diff --git a/components/resources/default_200_percent/autofill/visa.png b/components/resources/default_200_percent/autofill/visa.png
index 08e4015..e464fd6 100644
--- a/components/resources/default_200_percent/autofill/visa.png
+++ b/components/resources/default_200_percent/autofill/visa.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/amex.png b/components/resources/default_300_percent/autofill/amex.png
index 48a5a579..c41d6c6 100644
--- a/components/resources/default_300_percent/autofill/amex.png
+++ b/components/resources/default_300_percent/autofill/amex.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/cc-generic.png b/components/resources/default_300_percent/autofill/cc-generic.png
index 8861bbc0..15094939 100644
--- a/components/resources/default_300_percent/autofill/cc-generic.png
+++ b/components/resources/default_300_percent/autofill/cc-generic.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/credit_card_cvc_hint.png b/components/resources/default_300_percent/autofill/credit_card_cvc_hint.png
index 091974d..3574cb7 100644
--- a/components/resources/default_300_percent/autofill/credit_card_cvc_hint.png
+++ b/components/resources/default_300_percent/autofill/credit_card_cvc_hint.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/credit_card_cvc_hint_amex.png b/components/resources/default_300_percent/autofill/credit_card_cvc_hint_amex.png
index 931d8b17..22eae49 100644
--- a/components/resources/default_300_percent/autofill/credit_card_cvc_hint_amex.png
+++ b/components/resources/default_300_percent/autofill/credit_card_cvc_hint_amex.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/diners.png b/components/resources/default_300_percent/autofill/diners.png
index ed5aae44..f0451e4 100644
--- a/components/resources/default_300_percent/autofill/diners.png
+++ b/components/resources/default_300_percent/autofill/diners.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/discover.png b/components/resources/default_300_percent/autofill/discover.png
index ac4b02b..f221b82c 100644
--- a/components/resources/default_300_percent/autofill/discover.png
+++ b/components/resources/default_300_percent/autofill/discover.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/googlepay.png b/components/resources/default_300_percent/autofill/googlepay.png
index add38f1..4c04b3f 100644
--- a/components/resources/default_300_percent/autofill/googlepay.png
+++ b/components/resources/default_300_percent/autofill/googlepay.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/infobar_autofill_cc.png b/components/resources/default_300_percent/autofill/infobar_autofill_cc.png
index 870022b3..a58fe5f 100644
--- a/components/resources/default_300_percent/autofill/infobar_autofill_cc.png
+++ b/components/resources/default_300_percent/autofill/infobar_autofill_cc.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/infobar_autofill_googlepay_with_divider.png b/components/resources/default_300_percent/autofill/infobar_autofill_googlepay_with_divider.png
index 4f6c9eb6..76afa20 100644
--- a/components/resources/default_300_percent/autofill/infobar_autofill_googlepay_with_divider.png
+++ b/components/resources/default_300_percent/autofill/infobar_autofill_googlepay_with_divider.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/jcb.png b/components/resources/default_300_percent/autofill/jcb.png
index 628ca00..913fc0e9 100644
--- a/components/resources/default_300_percent/autofill/jcb.png
+++ b/components/resources/default_300_percent/autofill/jcb.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/mastercard.png b/components/resources/default_300_percent/autofill/mastercard.png
index d13be55..e273f35 100644
--- a/components/resources/default_300_percent/autofill/mastercard.png
+++ b/components/resources/default_300_percent/autofill/mastercard.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/migration_header.png b/components/resources/default_300_percent/autofill/migration_header.png
index 7cf5e26..ca1d6fa 100644
--- a/components/resources/default_300_percent/autofill/migration_header.png
+++ b/components/resources/default_300_percent/autofill/migration_header.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/mir.png b/components/resources/default_300_percent/autofill/mir.png
index cb8a805d..6bf41c0 100644
--- a/components/resources/default_300_percent/autofill/mir.png
+++ b/components/resources/default_300_percent/autofill/mir.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/unionpay.png b/components/resources/default_300_percent/autofill/unionpay.png
index 0ac0b38..ad9a889 100644
--- a/components/resources/default_300_percent/autofill/unionpay.png
+++ b/components/resources/default_300_percent/autofill/unionpay.png
Binary files differ
diff --git a/components/resources/default_300_percent/autofill/visa.png b/components/resources/default_300_percent/autofill/visa.png
index fc8ac65..9c2ac18 100644
--- a/components/resources/default_300_percent/autofill/visa.png
+++ b/components/resources/default_300_percent/autofill/visa.png
Binary files differ
diff --git a/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc b/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc
index ecb18aa..633c47b 100644
--- a/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc
+++ b/components/safe_browsing/android/safe_browsing_api_handler_bridge.cc
@@ -124,6 +124,9 @@
           callback_id));
 
   if (result_status != RESULT_STATUS_SUCCESS) {
+    CHECK(callback);              // Remove after fixing crbug.com/889972
+    CHECK(!callback->is_null());  // Remove after fixing crbug.com/889972
+
     if (result_status == RESULT_STATUS_TIMEOUT) {
       ReportUmaResult(UMA_STATUS_TIMEOUT);
       VLOG(1) << "Safe browsing API call timed-out";
@@ -138,10 +141,16 @@
 
   // Shortcut for safe, so we don't have to parse JSON.
   if (metadata_str == "{}") {
+    CHECK(callback);              // Remove after fixing crbug.com/889972
+    CHECK(!callback->is_null());  // Remove after fixing crbug.com/889972
+
     ReportUmaResult(UMA_STATUS_SAFE);
     RunCallbackOnIOThread(std::move(callback), SB_THREAT_TYPE_SAFE,
                           ThreatMetadata());
   } else {
+    CHECK(callback);              // Remove after fixing crbug.com/889972
+    CHECK(!callback->is_null());  // Remove after fixing crbug.com/889972
+
     // Unsafe, assuming we can parse the JSON.
     SBThreatType worst_threat;
     ThreatMetadata threat_metadata;
diff --git a/components/signin/core/browser/signin_metrics.cc b/components/signin/core/browser/signin_metrics.cc
index 6526d05..224ae04 100644
--- a/components/signin/core/browser/signin_metrics.cc
+++ b/components/signin/core/browser/signin_metrics.cc
@@ -106,10 +106,6 @@
       base::RecordAction(
           base::UserMetricsAction("Signin_Signin_FromTabSwitcher"));
       break;
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
-      base::RecordAction(
-          base::UserMetricsAction("Signin_Signin_FromForceSigninWarning"));
-      break;
     case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
       base::RecordAction(
           base::UserMetricsAction("Signin_Signin_FromSaveCardBubble"));
@@ -189,7 +185,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       NOTREACHED() << "Signin_SigninWithDefault_From* user actions"
                    << " are not recorded for access_point "
@@ -263,7 +258,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       NOTREACHED() << "Signin_SigninNotDefault_From* user actions"
                    << " are not recorded for access point "
@@ -337,7 +331,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       // These access points do not support personalized sign-in promos, so
       // |Signin_SigninNewAccountPreDice_From*| user actions should not
@@ -422,7 +415,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       // These access points do not support personalized sign-in promos, so
       // |Signin_SigninNewAccountNoExistingAccount_From*| user actions should
@@ -503,7 +495,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       // These access points do not support personalized sign-in promos, so
       // |Signin_SigninNewAccountExistingAccount_From*| user actions should not
@@ -956,7 +947,6 @@
       break;
     case AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case AccessPoint::ACCESS_POINT_EXTENSIONS:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case AccessPoint::ACCESS_POINT_USER_MANAGER:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
@@ -1088,7 +1078,6 @@
     case AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN:
     case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
-    case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
     case AccessPoint::ACCESS_POINT_MACHINE_LOGON:
       NOTREACHED() << "Signin_Impression{With|WithNo}Account_From* user actions"
                    << " are not recorded for access point "
diff --git a/components/signin/core/browser/signin_metrics.h b/components/signin/core/browser/signin_metrics.h
index 4529276..efd2db2 100644
--- a/components/signin/core/browser/signin_metrics.h
+++ b/components/signin/core/browser/signin_metrics.h
@@ -150,7 +150,6 @@
   ACCESS_POINT_NTP_CONTENT_SUGGESTIONS,
   ACCESS_POINT_RESIGNIN_INFOBAR,
   ACCESS_POINT_TAB_SWITCHER,
-  ACCESS_POINT_FORCE_SIGNIN_WARNING,
   ACCESS_POINT_SAVE_CARD_BUBBLE,
   ACCESS_POINT_MANAGE_CARDS_BUBBLE,
   ACCESS_POINT_MACHINE_LOGON,
diff --git a/components/signin/core/browser/signin_metrics_unittest.cc b/components/signin/core/browser/signin_metrics_unittest.cc
index 7f7e96f..06e3aaae8 100644
--- a/components/signin/core/browser/signin_metrics_unittest.cc
+++ b/components/signin/core/browser/signin_metrics_unittest.cc
@@ -100,8 +100,6 @@
         return "ReSigninInfobar";
       case AccessPoint::ACCESS_POINT_TAB_SWITCHER:
         return "TabSwitcher";
-      case AccessPoint::ACCESS_POINT_FORCE_SIGNIN_WARNING:
-        return "ForceSigninWarning";
       case AccessPoint::ACCESS_POINT_SAVE_CARD_BUBBLE:
         return "SaveCardBubble";
       case AccessPoint::ACCESS_POINT_MANAGE_CARDS_BUBBLE:
diff --git a/components/test/data/update_client/updatecheck_diff_reply_1.xml b/components/test/data/update_client/updatecheck_diff_reply_1.xml
deleted file mode 100644
index 093cb02..0000000
--- a/components/test/data/update_client/updatecheck_diff_reply_1.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
-    <updatecheck status='ok'>
-      <urls>
-        <url codebase='http://localhost/download/'/>
-      </urls>
-      <manifest version='1.0' prodversionmin='11.0.1.0'>
-        <packages>
-          <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx' fp='1'/>
-        </packages>
-      </manifest>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_diff_reply_2.xml b/components/test/data/update_client/updatecheck_diff_reply_2.xml
deleted file mode 100644
index 8ea2f3b..0000000
--- a/components/test/data/update_client/updatecheck_diff_reply_2.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
-    <updatecheck status='ok'>
-      <urls>
-        <url codebase='http://localhost/download/'/>
-        <url codebasediff='http://localhost/download/'/>
-      </urls>
-      <manifest version='2.0' prodversionmin='11.0.1.0'>
-        <packages>
-          <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx' namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx' fp='22'/>
-        </packages>
-      </manifest>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_diff_reply_3.xml b/components/test/data/update_client/updatecheck_diff_reply_3.xml
deleted file mode 100644
index faf14b0..0000000
--- a/components/test/data/update_client/updatecheck_diff_reply_3.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
-    <updatecheck status='noupdate'/>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_1.json b/components/test/data/update_client/updatecheck_reply_1.json
new file mode 100644
index 0000000..c9114dfc
--- /dev/null
+++ b/components/test/data/update_client/updatecheck_reply_1.json
@@ -0,0 +1,18 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"ok",
+   "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+   "actions":{"action":[{"run":"this"}]},
+   "manifest":{
+    "version":"1.0",
+    "prodversionmin":"11.0.1.0",
+    "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+    }
+   }
+ ]
+}}
diff --git a/components/test/data/update_client/updatecheck_reply_2.xml b/components/test/data/update_client/updatecheck_reply_2.xml
deleted file mode 100644
index 04b39683..0000000
--- a/components/test/data/update_client/updatecheck_reply_2.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
-    <updatecheck status='ok'>
-      <urls>
-        <url codebase='http://localhost/download/'/>
-      </urls>
-      <manifest version='2.0' prodversionmin='55.0.1.0'>
-        <packages>
-          <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'/>
-        </packages>
-      </manifest>
-    </updatecheck>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_3.xml b/components/test/data/update_client/updatecheck_reply_3.xml
deleted file mode 100644
index 819ad10..0000000
--- a/components/test/data/update_client/updatecheck_reply_3.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<response protocol='3.1'>
-  <app appid='abagagagagagagagagagagagagagagag'>
-    <updatecheck status='noupdate'/>
-  </app>
-</response>
diff --git a/components/test/data/update_client/updatecheck_reply_4.json b/components/test/data/update_client/updatecheck_reply_4.json
new file mode 100644
index 0000000..38580ab
--- /dev/null
+++ b/components/test/data/update_client/updatecheck_reply_4.json
@@ -0,0 +1,18 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":3383},
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"ok",
+   "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+   "manifest":{
+    "version":"1.0",
+    "prodversionmin":"11.0.1.0",
+    "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+    }
+   }
+ ]
+}}
diff --git a/components/test/data/update_client/updatecheck_reply_empty b/components/test/data/update_client/updatecheck_reply_empty
deleted file mode 100644
index 0519ecba..0000000
--- a/components/test/data/update_client/updatecheck_reply_empty
+++ /dev/null
@@ -1 +0,0 @@
- 
\ No newline at end of file
diff --git a/components/test/data/update_client/updatecheck_reply_noupdate.json b/components/test/data/update_client/updatecheck_reply_noupdate.json
new file mode 100644
index 0000000..d069050a
--- /dev/null
+++ b/components/test/data/update_client/updatecheck_reply_noupdate.json
@@ -0,0 +1,14 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_days":3383},
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"ok",
+   "updatecheck":{
+   "status":"noupdate",
+   "actions":{"action":[{"run":"this"}]}
+   }
+  }
+ ]
+}}
diff --git a/components/test/data/update_client/updatecheck_reply_parse_error.json b/components/test/data/update_client/updatecheck_reply_parse_error.json
new file mode 100644
index 0000000..82f95fc
--- /dev/null
+++ b/components/test/data/update_client/updatecheck_reply_parse_error.json
@@ -0,0 +1,7 @@
+)]}'
+{"response":{
+ "protocol":"3.0",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf"}
+ ]
+}}
diff --git a/components/test/data/update_client/updatecheck_reply_unknownapp.json b/components/test/data/update_client/updatecheck_reply_unknownapp.json
new file mode 100644
index 0000000..5f1b522
--- /dev/null
+++ b/components/test/data/update_client/updatecheck_reply_unknownapp.json
@@ -0,0 +1,9 @@
+)]}'
+{"response":{
+ "protocol":"3.1",
+ "app":[
+  {"appid":"jebgalgnebhfojomionfpkfelancnnkf",
+   "status":"error-unknownApplication"
+  }
+ ]
+}}
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
index 593c0442..20625d5 100644
--- a/components/update_client/BUILD.gn
+++ b/components/update_client/BUILD.gn
@@ -36,10 +36,14 @@
     "protocol_handler.h",
     "protocol_parser.cc",
     "protocol_parser.h",
+    "protocol_parser_json.cc",
+    "protocol_parser_json.h",
     "protocol_parser_xml.cc",
     "protocol_parser_xml.h",
     "protocol_serializer.cc",
     "protocol_serializer.h",
+    "protocol_serializer_json.cc",
+    "protocol_serializer_json.h",
     "protocol_serializer_xml.cc",
     "protocol_serializer_xml.h",
     "request_sender.cc",
@@ -139,10 +143,15 @@
     "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx",
     "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx",
     "//components/test/data/update_client/runaction_test_win.crx3",
+    "//components/test/data/update_client/updatecheck_reply_1.json",
     "//components/test/data/update_client/updatecheck_reply_1.xml",
+    "//components/test/data/update_client/updatecheck_reply_4.json",
     "//components/test/data/update_client/updatecheck_reply_4.xml",
+    "//components/test/data/update_client/updatecheck_reply_noupdate.json",
     "//components/test/data/update_client/updatecheck_reply_noupdate.xml",
+    "//components/test/data/update_client/updatecheck_reply_parse_error.json",
     "//components/test/data/update_client/updatecheck_reply_parse_error.xml",
+    "//components/test/data/update_client/updatecheck_reply_unknownapp.json",
     "//components/test/data/update_client/updatecheck_reply_unknownapp.xml",
   ]
   outputs = [
@@ -159,7 +168,9 @@
     "component_unpacker_unittest.cc",
     "persisted_data_unittest.cc",
     "ping_manager_unittest.cc",
+    "protocol_parser_json_unittest.cc",
     "protocol_parser_xml_unittest.cc",
+    "protocol_serializer_json_unittest.cc",
     "protocol_serializer_unittest.cc",
     "protocol_serializer_xml_unittest.cc",
     "request_sender_unittest.cc",
diff --git a/components/update_client/component.cc b/components/update_client/component.cc
index ad999cd..59e50fc 100644
--- a/components/update_client/component.cc
+++ b/components/update_client/component.cc
@@ -20,6 +20,7 @@
 #include "components/update_client/action_runner.h"
 #include "components/update_client/component_unpacker.h"
 #include "components/update_client/configurator.h"
+#include "components/update_client/protocol_definition.h"
 #include "components/update_client/protocol_serializer.h"
 #include "components/update_client/task_traits.h"
 #include "components/update_client/update_client.h"
@@ -372,15 +373,15 @@
   event.SetKey("url", base::Value(dm.url.spec()));
 
   // -1 means that the  byte counts are not known.
-  if (dm.total_bytes != -1)
-    event.SetKey("total", base::Value(base::NumberToString(dm.total_bytes)));
-  if (dm.downloaded_bytes != -1) {
+  if (dm.total_bytes != -1 && dm.total_bytes < kProtocolMaxInt)
+    event.SetKey("total", base::Value(static_cast<double>(dm.total_bytes)));
+  if (dm.downloaded_bytes != -1 && dm.total_bytes < kProtocolMaxInt) {
     event.SetKey("downloaded",
-                 base::Value(base::NumberToString(dm.downloaded_bytes)));
+                 base::Value(static_cast<double>(dm.downloaded_bytes)));
   }
-  if (dm.download_time_ms) {
+  if (dm.download_time_ms && dm.total_bytes < kProtocolMaxInt) {
     event.SetKey("download_time_ms",
-                 base::Value(base::NumberToString(dm.download_time_ms)));
+                 base::Value(static_cast<double>(dm.download_time_ms)));
   }
   DCHECK(previous_version().IsValid());
   event.SetKey("previousversion", base::Value(previous_version().GetString()));
diff --git a/components/update_client/ping_manager_unittest.cc b/components/update_client/ping_manager_unittest.cc
index 503bf2b..e33a755 100644
--- a/components/update_client/ping_manager_unittest.cc
+++ b/components/update_client/ping_manager_unittest.cc
@@ -4,18 +4,23 @@
 
 #include "components/update_client/ping_manager.h"
 
+#include <stdint.h>
+
+#include <limits>
 #include <memory>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "base/bind.h"
+#include "base/json/json_reader.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/version.h"
 #include "components/update_client/component.h"
+#include "components/update_client/protocol_definition.h"
 #include "components/update_client/protocol_serializer.h"
 #include "components/update_client/test_configurator.h"
 #include "components/update_client/update_engine.h"
@@ -28,7 +33,8 @@
 
 namespace update_client {
 
-class PingManagerTest : public testing::Test {
+class PingManagerTest : public testing::Test,
+                        public testing::WithParamInterface<bool> {
  public:
   PingManagerTest();
   ~PingManagerTest() override {}
@@ -49,6 +55,8 @@
   scoped_refptr<TestConfigurator> config_;
   scoped_refptr<PingManager> ping_manager_;
 
+  bool use_JSON_ = false;
+
   int error_ = -1;
   std::string response_;
 
@@ -64,6 +72,8 @@
 }
 
 void PingManagerTest::SetUp() {
+  use_JSON_ = GetParam();
+  config_->SetUseJSON(use_JSON_);
   ping_manager_ = base::MakeRefCounted<PingManager>(config_);
 }
 
@@ -103,13 +113,16 @@
       UpdateEngine::Callback(), nullptr);
 }
 
-TEST_F(PingManagerTest, SendPing) {
+// This test is parameterized for using JSON or XML serialization. |true| means
+// JSON serialization is used.
+INSTANTIATE_TEST_CASE_P(Parameterized, PingManagerTest, testing::Bool());
+
+TEST_P(PingManagerTest, SendPing) {
   auto interceptor = std::make_unique<URLLoaderPostInterceptor>(
       config_->test_url_loader_factory());
   EXPECT_TRUE(interceptor);
 
   const auto update_context = MakeMockUpdateContext();
-
   {
     // Test eventresult="1" is sent for successful updates.
     Component component(*update_context, "abc");
@@ -126,23 +139,60 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<\?xml version="1\.0" encoding="UTF-8"\?>)"
-        R"(<request protocol="3\.1" )"
-        R"(dedup="cr" acceptformat="crx2,crx3" extra="foo" )"
-        R"(sessionid="{[-\w]{36}}" requestid="{[-\w]{36}}" )"
-        R"(updater="fake_prodid" updaterversion="30\.0" prodversion="30\.0" )"
-        R"(lang="fake_lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" )"
-        R"((wow64="1" )?)"
-        R"(updaterchannel="fake_channel_string" )"
-        R"(prodchannel="fake_channel_string">)"
-        R"(<hw physmemory="[0-9]+"/>)"
-        R"(<os platform="Fake Operating System" arch="[,-.\w]+" )"
-        R"(version="[-.\w]+"( sp="[\s\w]+")?/>)"
-        R"(<app appid="abc" version="1\.0">)"
-        R"(<event eventresult="1" eventtype="3" )"
-        R"(nextversion="2\.0" previousversion="1\.0"/></app></request>)";
-    EXPECT_TRUE(RE2::FullMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      ASSERT_TRUE(request);
+      EXPECT_TRUE(request->FindKey("@os"));
+      EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+      EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+      EXPECT_TRUE(request->FindKey("arch"));
+      EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+      EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+      EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+      EXPECT_TRUE(request->FindKey("nacl_arch"));
+      EXPECT_EQ("fake_channel_string",
+                request->FindKey("prodchannel")->GetString());
+      EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+      EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+      EXPECT_TRUE(request->FindKey("requestid"));
+      EXPECT_TRUE(request->FindKey("sessionid"));
+      EXPECT_EQ("fake_channel_string",
+                request->FindKey("updaterchannel")->GetString());
+      EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+
+      EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+      EXPECT_EQ("Fake Operating System",
+                request->FindPath({"os", "platform"})->GetString());
+      EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    } else {
+      constexpr char regex[] =
+          R"(<\?xml version="1\.0" encoding="UTF-8"\?>)"
+          R"(<request protocol="3\.1" )"
+          R"(dedup="cr" acceptformat="crx2,crx3" extra="foo" )"
+          R"(sessionid="{[-\w]{36}}" requestid="{[-\w]{36}}" )"
+          R"(updater="fake_prodid" updaterversion="30\.0" prodversion="30\.0" )"
+          R"(lang="fake_lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" )"
+          R"((wow64="1" )?)"
+          R"(updaterchannel="fake_channel_string" )"
+          R"(prodchannel="fake_channel_string">)"
+          R"(<hw physmemory="[0-9]+"/>)"
+          R"(<os platform="Fake Operating System" arch="[,-.\w]+" )"
+          R"(version="[-.\w]+"( sp="[\s\w]+")?/>)"
+          R"(<app appid="abc" version="1\.0">)"
+          R"(<event eventresult="1" eventtype="3" )"
+          R"(nextversion="2\.0" previousversion="1\.0"/></app></request>)";
+      EXPECT_TRUE(RE2::FullMatch(msg, regex)) << msg;
+    }
 
     // Check the ping request does not carry the specific extra request headers.
     const auto headers = std::get<1>(interceptor->GetRequests()[0]);
@@ -169,11 +219,24 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<app appid="abc" version="1\.0">)"
-        R"(<event eventresult="0" eventtype="3" )"
-        R"(nextversion="2\.0" previousversion="1\.0"/></app>)";
-    EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    } else {
+      constexpr char regex[] =
+          R"(<app appid="abc" version="1\.0">)"
+          R"(<event eventresult="0" eventtype="3" )"
+          R"(nextversion="2\.0" previousversion="1\.0"/></app>)";
+      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    }
     interceptor->Reset();
   }
 
@@ -203,14 +266,36 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<app appid="abc" version="1\.0">)"
-        R"(<event differrorcat="4" differrorcode="20" )"
-        R"(diffextracode1="-10" diffresult="0" errorcat="1" errorcode="2" )"
-        R"(eventresult="0" eventtype="3" extracode1="-1" nextfp="next fp" )"
-        R"(nextversion="2\.0" previousfp="prev fp" previousversion="1\.0"/>)"
-        R"(</app>)";
-    EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+      EXPECT_EQ(4, event.FindKey("differrorcat")->GetInt());
+      EXPECT_EQ(20, event.FindKey("differrorcode")->GetInt());
+      EXPECT_EQ(-10, event.FindKey("diffextracode1")->GetInt());
+      EXPECT_EQ(0, event.FindKey("diffresult")->GetInt());
+      EXPECT_EQ(1, event.FindKey("errorcat")->GetInt());
+      EXPECT_EQ(2, event.FindKey("errorcode")->GetInt());
+      EXPECT_EQ(-1, event.FindKey("extracode1")->GetInt());
+      EXPECT_EQ("next fp", event.FindKey("nextfp")->GetString());
+      EXPECT_EQ("prev fp", event.FindKey("previousfp")->GetString());
+    } else {
+      constexpr char regex[] =
+          R"(<app appid="abc" version="1\.0">)"
+          R"(<event differrorcat="4" differrorcode="20" )"
+          R"(diffextracode1="-10" diffresult="0" errorcat="1" errorcode="2" )"
+          R"(eventresult="0" eventtype="3" extracode1="-1" nextfp="next fp" )"
+          R"(nextversion="2\.0" previousfp="prev fp" previousversion="1\.0"/>)"
+          R"(</app>)";
+      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    }
     interceptor->Reset();
   }
 
@@ -231,11 +316,23 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<app appid="abc" version="1\.0">)"
-        R"(<event eventresult="0" eventtype="3" previousversion="1\.0"/>)"
-        R"(</app>)";
-    EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+    } else {
+      constexpr char regex[] =
+          R"(<app appid="abc" version="1\.0">)"
+          R"(<event eventresult="0" eventtype="3" previousversion="1\.0"/>)"
+          R"(</app>)";
+      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    }
     interceptor->Reset();
   }
 
@@ -253,11 +350,24 @@
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<app appid="abc" version="1\.2\.3\.4">)"
-        R"(<event eventresult="1" eventtype="4" )"
-        R"(nextversion="0" previousversion="1\.2\.3\.4"/></app>)";
-    EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.2.3.4", app.FindKey("version")->GetString());
+      const auto& event = app.FindKey("event")->GetList()[0];
+      EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+      EXPECT_EQ(4, event.FindKey("eventtype")->GetInt());
+      EXPECT_EQ("1.2.3.4", event.FindKey("previousversion")->GetString());
+      EXPECT_EQ("0", event.FindKey("nextversion")->GetString());
+    } else {
+      constexpr char regex[] =
+          R"(<app appid="abc" version="1\.2\.3\.4">)"
+          R"(<event eventresult="1" eventtype="4" )"
+          R"(nextversion="0" previousversion="1\.2\.3\.4"/></app>)";
+      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    }
     interceptor->Reset();
   }
 
@@ -289,31 +399,101 @@
     download_metrics.download_time_ms = 9870;
     component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
 
+    download_metrics = CrxDownloader::DownloadMetrics();
+    download_metrics.url = GURL("http://host3/path3");
+    download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits;
+    download_metrics.error = 0;
+    download_metrics.downloaded_bytes = kProtocolMaxInt;
+    download_metrics.total_bytes = kProtocolMaxInt - 1;
+    download_metrics.download_time_ms = kProtocolMaxInt - 2;
+    component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
     EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
     ping_manager_->SendPing(component, MakePingCallback());
     RunThreads();
 
     EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
     const auto msg = interceptor->GetRequestBody(0);
-    constexpr char regex[] =
-        R"(<app appid="abc" version="1\.0">)"
-        R"(<event eventresult="1" eventtype="3" )"
-        R"(nextversion="2\.0" previousversion="1\.0"/>)"
-        R"(<event download_time_ms="987" downloaded="123" downloader="direct" )"
-        R"(errorcode="-1" eventresult="0" eventtype="14" )"
-        R"(nextversion="2\.0" previousversion="1\.0" total="456" )"
-        R"(url="http://host1/path1"/>)"
-        R"(<event download_time_ms="9870" downloaded="1230" downloader="bits" )"
-        R"(eventresult="1" eventtype="14" nextversion="2\.0" )"
-        R"(previousversion="1\.0" total="4560" url="http://host2/path2"/></app>)";
-    EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(msg);
+      const auto* request = root->FindKey("request");
+      const auto& app = request->FindKey("app")->GetList()[0];
+      EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+      EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+      EXPECT_EQ(4u, app.FindKey("event")->GetList().size());
+      {
+        const auto& event = app.FindKey("event")->GetList()[0];
+        EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+        EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+        EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+        EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+      }
+      {
+        const auto& event = app.FindKey("event")->GetList()[1];
+        EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+        EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+        EXPECT_EQ(987, event.FindKey("download_time_ms")->GetDouble());
+        EXPECT_EQ(123, event.FindKey("downloaded")->GetDouble());
+        EXPECT_EQ("direct", event.FindKey("downloader")->GetString());
+        EXPECT_EQ(-1, event.FindKey("errorcode")->GetInt());
+        EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+        EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+        EXPECT_EQ(456, event.FindKey("total")->GetDouble());
+        EXPECT_EQ("http://host1/path1", event.FindKey("url")->GetString());
+      }
+      {
+        const auto& event = app.FindKey("event")->GetList()[2];
+        EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+        EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+        EXPECT_EQ(9870, event.FindKey("download_time_ms")->GetDouble());
+        EXPECT_EQ(1230, event.FindKey("downloaded")->GetDouble());
+        EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+        EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+        EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+        EXPECT_EQ(4560, event.FindKey("total")->GetDouble());
+        EXPECT_EQ("http://host2/path2", event.FindKey("url")->GetString());
+      }
+      {
+        const auto& event = app.FindKey("event")->GetList()[3];
+        EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+        EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+        EXPECT_EQ(9007199254740990,
+                  event.FindKey("download_time_ms")->GetDouble());
+        EXPECT_EQ(9007199254740992, event.FindKey("downloaded")->GetDouble());
+        EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+        EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+        EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+        EXPECT_EQ(9007199254740991, event.FindKey("total")->GetDouble());
+        EXPECT_EQ("http://host3/path3", event.FindKey("url")->GetString());
+      }
+    } else {
+      constexpr char regex[] =
+          R"(<app appid="abc" version="1\.0">)"
+          R"(<event eventresult="1" eventtype="3" )"
+          R"(nextversion="2\.0" previousversion="1\.0"/>)"
+          R"(<event download_time_ms="987" )"
+          R"(downloaded="123" downloader="direct" )"
+          R"(errorcode="-1" eventresult="0" eventtype="14" )"
+          R"(nextversion="2\.0" previousversion="1\.0" total="456" )"
+          R"(url="http://host1/path1"/>)"
+          R"(<event download_time_ms="9870" downloaded="1230" )"
+          R"(downloader="bits" )"
+          R"(eventresult="1" eventtype="14" nextversion="2\.0" )"
+          R"(previousversion="1\.0" total="4560" url="http://host2/path2"/>)"
+          R"(<event download_time_ms="9007199254740990" )"
+          R"(downloaded="9007199254740992" downloader="bits" )"
+          R"(eventresult="1" eventtype="14" nextversion="2.0" )"
+          R"(previousversion="1.0" total="9007199254740991" )"
+          R"(url="http://host3/path3"/></app>)";
+      EXPECT_TRUE(RE2::PartialMatch(msg, regex)) << msg;
+    }
     interceptor->Reset();
   }
 }
 
 // Tests that sending the ping fails when the component requires encryption but
 // the ping URL is unsecure.
-TEST_F(PingManagerTest, RequiresEncryption) {
+TEST_P(PingManagerTest, RequiresEncryption) {
   config_->SetPingUrl(GURL("http:\\foo\bar"));
 
   const auto update_context = MakeMockUpdateContext();
diff --git a/components/update_client/protocol_definition.h b/components/update_client/protocol_definition.h
index e5c4f5b..aaacc76 100644
--- a/components/update_client/protocol_definition.h
+++ b/components/update_client/protocol_definition.h
@@ -13,12 +13,9 @@
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/optional.h"
+#include "base/values.h"
 #include "build/build_config.h"
 
-namespace base {
-class Value;
-}
-
 namespace update_client {
 
 // The protocol versions so far are:
@@ -26,6 +23,10 @@
 // * Version 3.0: it is the version implemented by the desktop updaters.
 constexpr char kProtocolVersion[] = "3.1";
 
+// Due to implementation constraints of the JSON parser and serializer,
+// precision of integer numbers greater than 2^53 is lost.
+constexpr int64_t kProtocolMaxInt = 1LL << 53;
+
 namespace protocol_request {
 
 struct HW {
diff --git a/components/update_client/protocol_handler.cc b/components/update_client/protocol_handler.cc
index e301461d..a6b4999 100644
--- a/components/update_client/protocol_handler.cc
+++ b/components/update_client/protocol_handler.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_parser_json.h"
 #include "components/update_client/protocol_parser_xml.h"
+#include "components/update_client/protocol_serializer_json.h"
 #include "components/update_client/protocol_serializer_xml.h"
 
 namespace update_client {
@@ -18,4 +20,14 @@
   return std::make_unique<ProtocolSerializerXml>();
 }
 
+std::unique_ptr<ProtocolParser> ProtocolHandlerFactoryJSON::CreateParser()
+    const {
+  return std::make_unique<ProtocolParserJSON>();
+}
+
+std::unique_ptr<ProtocolSerializer>
+ProtocolHandlerFactoryJSON::CreateSerializer() const {
+  return std::make_unique<ProtocolSerializerJSON>();
+}
+
 }  // namespace update_client
diff --git a/components/update_client/protocol_handler.h b/components/update_client/protocol_handler.h
index 40ed804..cb8e97ed 100644
--- a/components/update_client/protocol_handler.h
+++ b/components/update_client/protocol_handler.h
@@ -34,6 +34,13 @@
   std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
 };
 
+class ProtocolHandlerFactoryJSON final : public ProtocolHandlerFactory {
+ public:
+  // Overrides for ProtocolHandlerFactory.
+  std::unique_ptr<ProtocolParser> CreateParser() const override;
+  std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
+};
+
 }  // namespace update_client
 
 #endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
diff --git a/components/update_client/protocol_parser.h b/components/update_client/protocol_parser.h
index 58fea15f..6a0dbff 100644
--- a/components/update_client/protocol_parser.h
+++ b/components/update_client/protocol_parser.h
@@ -25,6 +25,8 @@
         Package(const Package& other);
         ~Package();
 
+        // |fingerprint| is optional. It identifies the package, preferably
+        // with a modified sha256 hash of the package in hex format.
         std::string fingerprint;
 
         // Attributes for the full update.
diff --git a/components/update_client/protocol_parser_json.cc b/components/update_client/protocol_parser_json.cc
new file mode 100644
index 0000000..3ff9f40
--- /dev/null
+++ b/components/update_client/protocol_parser_json.cc
@@ -0,0 +1,330 @@
+// 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 "components/update_client/protocol_parser_json.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/update_client/protocol_definition.h"
+
+namespace update_client {
+
+namespace {
+
+bool ParseManifest(const base::Value& manifest_node,
+                   ProtocolParser::Result* result,
+                   std::string* error) {
+  if (!manifest_node.is_dict()) {
+    *error = "'manifest' is not a dictionary.";
+  }
+  const auto* version = manifest_node.FindKey("version");
+  if (!version || !version->is_string()) {
+    *error = "Missing version for manifest.";
+    return false;
+  }
+
+  result->manifest.version = version->GetString();
+  if (!base::Version(result->manifest.version).IsValid()) {
+    *error =
+        base::StrCat({"Invalid version: '", result->manifest.version, "'."});
+    return false;
+  }
+
+  // Get the optional minimum browser version.
+  const auto* browser_min_version = manifest_node.FindKey("prodversionmin");
+  if (browser_min_version && browser_min_version->is_string()) {
+    result->manifest.browser_min_version = browser_min_version->GetString();
+    if (!base::Version(result->manifest.browser_min_version).IsValid()) {
+      *error = base::StrCat({"Invalid prodversionmin: '",
+                             result->manifest.browser_min_version, "'."});
+      return false;
+    }
+  }
+
+  const auto* packages_node = manifest_node.FindKey("packages");
+  if (!packages_node || !packages_node->is_dict()) {
+    *error = "Missing packages in manifest or 'packages' is not a dictionary.";
+    return false;
+  }
+  const auto* package_node = packages_node->FindKey("package");
+  if (!package_node || !package_node->is_list()) {
+    *error = "Missing package in packages.";
+    return false;
+  }
+
+  for (const auto& package : package_node->GetList()) {
+    if (!package.is_dict()) {
+      *error = "'package' is not a dictionary.";
+      return false;
+    }
+    ProtocolParser::Result::Manifest::Package p;
+    const auto* name = package.FindKey("name");
+    if (!name || !name->is_string()) {
+      *error = "Missing name for package.";
+      return false;
+    }
+    p.name = name->GetString();
+
+    const auto* namediff = package.FindKey("namediff");
+    if (namediff && namediff->is_string())
+      p.namediff = namediff->GetString();
+
+    const auto* fingerprint = package.FindKey("fp");
+    if (fingerprint && fingerprint->is_string())
+      p.fingerprint = fingerprint->GetString();
+
+    const auto* hash_sha256 = package.FindKey("hash_sha256");
+    if (hash_sha256 && hash_sha256->is_string())
+      p.hash_sha256 = hash_sha256->GetString();
+
+    const auto* size = package.FindKey("size");
+    if (size && size->is_int())
+      p.size = size->GetInt();
+
+    const auto* hashdiff_sha256 = package.FindKey("hashdiff_sha256");
+    if (hashdiff_sha256 && hashdiff_sha256->is_string())
+      p.hashdiff_sha256 = hashdiff_sha256->GetString();
+
+    const auto* sizediff = package.FindKey("sizediff");
+    if (sizediff && sizediff->is_int())
+      p.sizediff = sizediff->GetInt();
+
+    result->manifest.packages.push_back(std::move(p));
+  }
+
+  return true;
+}
+
+void ParseActions(const base::Value& actions_node,
+                  ProtocolParser::Result* result) {
+  if (!actions_node.is_dict())
+    return;
+
+  const auto* action_node = actions_node.FindKey("action");
+  if (!action_node || !action_node->is_list())
+    return;
+
+  const auto& action_list = action_node->GetList();
+  if (action_list.empty() || !action_list[0].is_dict())
+    return;
+
+  const auto* run = action_list[0].FindKey("run");
+  if (run && run->is_string())
+    result->action_run = run->GetString();
+}
+
+bool ParseUrls(const base::Value& urls_node,
+               ProtocolParser::Result* result,
+               std::string* error) {
+  if (!urls_node.is_dict()) {
+    *error = "'urls' is not a dictionary.";
+    return false;
+  }
+  const auto* url_node = urls_node.FindKey("url");
+  if (!url_node || !url_node->is_list()) {
+    *error = "Missing url on urls.";
+    return false;
+  }
+
+  for (const auto& url : url_node->GetList()) {
+    if (!url.is_dict())
+      continue;
+    const auto* codebase = url.FindKey("codebase");
+    if (codebase && codebase->is_string()) {
+      GURL crx_url(codebase->GetString());
+      if (crx_url.is_valid())
+        result->crx_urls.push_back(std::move(crx_url));
+    }
+    const auto* codebasediff = url.FindKey("codebasediff");
+    if (codebasediff && codebasediff->is_string()) {
+      GURL crx_diffurl(codebasediff->GetString());
+      if (crx_diffurl.is_valid())
+        result->crx_diffurls.push_back(std::move(crx_diffurl));
+    }
+  }
+
+  // Expect at least one url for full update.
+  if (result->crx_urls.empty()) {
+    *error = "Missing valid url for full update.";
+    return false;
+  }
+
+  return true;
+}
+
+bool ParseUpdateCheck(const base::Value& updatecheck_node,
+                      ProtocolParser::Result* result,
+                      std::string* error) {
+  if (!updatecheck_node.is_dict()) {
+    *error = "'updatecheck' is not a dictionary.";
+    return false;
+  }
+  const auto* status = updatecheck_node.FindKey("status");
+  if (!status || !status->is_string()) {
+    *error = "Missing status on updatecheck node";
+    return false;
+  }
+
+  result->status = status->GetString();
+  if (result->status == "noupdate") {
+    const auto* actions_node = updatecheck_node.FindKey("actions");
+    if (actions_node)
+      ParseActions(*actions_node, result);
+    return true;
+  }
+
+  if (result->status == "ok") {
+    const auto* actions_node = updatecheck_node.FindKey("actions");
+    if (actions_node)
+      ParseActions(*actions_node, result);
+
+    const auto* urls_node = updatecheck_node.FindKey("urls");
+    if (!urls_node) {
+      *error = "Missing urls on updatecheck.";
+      return false;
+    }
+
+    if (!ParseUrls(*urls_node, result, error))
+      return false;
+
+    const auto* manifest_node = updatecheck_node.FindKey("manifest");
+    if (!manifest_node) {
+      *error = "Missing manifest on updatecheck.";
+      return false;
+    }
+    return ParseManifest(*manifest_node, result, error);
+  }
+
+  // Return the |updatecheck| element status as a parsing error.
+  *error = result->status;
+  return false;
+}
+
+bool ParseApp(const base::Value& app_node,
+              ProtocolParser::Result* result,
+              std::string* error) {
+  if (!app_node.is_dict()) {
+    *error = "'app' is not a dictionary.";
+    return false;
+  }
+  for (const auto* cohort_key :
+       {ProtocolParser::Result::kCohort, ProtocolParser::Result::kCohortHint,
+        ProtocolParser::Result::kCohortName}) {
+    const auto* cohort_value = app_node.FindKey(cohort_key);
+    if (cohort_value && cohort_value->is_string())
+      result->cohort_attrs[cohort_key] = cohort_value->GetString();
+  }
+  const auto* appid = app_node.FindKey("appid");
+  if (appid && appid->is_string())
+    result->extension_id = appid->GetString();
+  if (result->extension_id.empty()) {
+    *error = "Missing appid on app node";
+    return false;
+  }
+
+  // Read the |status| attribute for the app.
+  // If the status is one of the defined app status error literals, then return
+  // it in the result as if it were an updatecheck status, then stop parsing,
+  // and return success.
+  const auto* status = app_node.FindKey("status");
+  if (status && status->is_string()) {
+    result->status = status->GetString();
+    if (result->status == "restricted" ||
+        result->status == "error-unknownApplication" ||
+        result->status == "error-invalidAppId")
+      return true;
+
+    // If the status was not handled above and the status is not "ok", then
+    // this must be a status literal that that the parser does not know about.
+    if (!result->status.empty() && result->status != "ok") {
+      *error = "Unknown app status";
+      return false;
+    }
+  }
+
+  DCHECK(result->status.empty() || result->status == "ok");
+  const auto* updatecheck_node = app_node.FindKey("updatecheck");
+  if (!updatecheck_node) {
+    *error = "Missing updatecheck on app.";
+    return false;
+  }
+
+  return ParseUpdateCheck(*updatecheck_node, result, error);
+}
+
+}  // namespace
+
+bool ProtocolParserJSON::DoParse(const std::string& response_json,
+                                 Results* results) {
+  DCHECK(results);
+
+  if (response_json.empty()) {
+    ParseError("Empty JSON.");
+    return false;
+  }
+
+  // The JSON response contains a prefix to prevent XSSI.
+  constexpr char kJSONPrefix[] = ")]}'";
+  if (!base::StartsWith(response_json, kJSONPrefix,
+                        base::CompareCase::SENSITIVE)) {
+    ParseError("Missing secure JSON prefix.");
+    return false;
+  }
+  const auto doc = base::JSONReader().Read(
+      {response_json.begin() + std::char_traits<char>::length(kJSONPrefix),
+       response_json.end()});
+  if (!doc) {
+    ParseError("JSON read error.");
+    return false;
+  }
+  if (!doc->is_dict()) {
+    ParseError("JSON document is not a dictionary.");
+    return false;
+  }
+  const auto* response_node = doc->FindKey("response");
+  if (!response_node || !response_node->is_dict()) {
+    ParseError("Missing 'response' element or 'response' is not a dictionary.");
+    return false;
+  }
+  const auto* protocol = response_node->FindKey("protocol");
+  if (!protocol || !protocol->is_string() ||
+      protocol->GetString() != kProtocolVersion) {
+    ParseError(
+        "Missing/incorrect protocol."
+        "(expected '%s', found '%s')",
+        kProtocolVersion, protocol->GetString().c_str());
+    return false;
+  }
+
+  const auto* daystart_node = response_node->FindKey("daystart");
+  if (daystart_node && daystart_node->is_dict()) {
+    const auto* elapsed_seconds = daystart_node->FindKey("elapsed_seconds");
+    if (elapsed_seconds && elapsed_seconds->is_int())
+      results->daystart_elapsed_seconds = elapsed_seconds->GetInt();
+    const auto* elapsed_days = daystart_node->FindKey("elapsed_days");
+    if (elapsed_days && elapsed_days->is_int())
+      results->daystart_elapsed_days = elapsed_days->GetInt();
+  }
+
+  const auto* app_node = response_node->FindKey("app");
+  if (app_node && app_node->is_list()) {
+    for (const auto& app : app_node->GetList()) {
+      Result result;
+      std::string error;
+      if (ParseApp(app, &result, &error))
+        results->list.push_back(result);
+      else
+        ParseError("%s", error.c_str());
+    }
+  }
+
+  return true;
+}
+
+}  // namespace update_client
diff --git a/components/update_client/protocol_parser_json.h b/components/update_client/protocol_parser_json.h
new file mode 100644
index 0000000..71f96f44
--- /dev/null
+++ b/components/update_client/protocol_parser_json.h
@@ -0,0 +1,30 @@
+// 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/update_client/protocol_parser.h"
+
+namespace update_client {
+
+// Parses responses for the update protocol version 3.
+// (https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md)
+class ProtocolParserJSON final : public ProtocolParser {
+ public:
+  ProtocolParserJSON() = default;
+
+ private:
+  // Overrides for ProtocolParser.
+  bool DoParse(const std::string& response_json, Results* results) override;
+
+  DISALLOW_COPY_AND_ASSIGN(ProtocolParserJSON);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
diff --git a/components/update_client/protocol_parser_json_unittest.cc b/components/update_client/protocol_parser_json_unittest.cc
new file mode 100644
index 0000000..49f897d
--- /dev/null
+++ b/components/update_client/protocol_parser_json_unittest.cc
@@ -0,0 +1,492 @@
+// 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 "components/update_client/protocol_parser_json.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+const char* kJSONValid = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONHash = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx",
+                              "hash_sha256":"1234",
+                              "hashdiff_sha256":"5678"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidSizes = R"()]}'
+ {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"1","size":1234},
+                             {"name":"1","size":-1234},
+                             {"name":"1"},
+                             {"name":"1","size":"-a"},
+                             {"name":"1","size":-123467890123456789},
+                             {"name":"1","size":123467890123456789}]}}
+      }
+     }
+    ]
+   }})";
+
+const char* kJSONInvalidMissingCodebase = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebasediff":"http://diff.example.com"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"namediff":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidMissingManifest = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONMissingAppId = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidCodebase = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"example.com/extension_1.2.3.4.crx",
+                     "version":"1.2.3.4"}]}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONMissingVersion = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+     "manifest":{
+      "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+const char* kJSONInvalidVersion = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+     "manifest":{
+      "version":"1.2.3.a",
+      "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a <daystart> tag.
+const char* kJSONWithDaystart = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Indicates no updates available.
+const char* kJSONNoUpdate = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "status":"ok",
+     "updatecheck":{
+     "status":"noupdate"
+     }
+    }
+   ]
+  }})";
+
+// Includes two app objects, one app with an error.
+const char* kJSONTwoAppsOneError = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"aaaaaaaa",
+     "status":"error-unknownApplication",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"bbbbbbbb",
+     "status":"ok",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes two <app> tags, both of which set the cohort.
+const char* kJSONTwoAppsSetCohort = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "daystart":{"elapsed_seconds":456},
+   "app":[
+    {"appid":"aaaaaaaa",
+     "cohort":"1:2q3/",
+     "updatecheck":{"status":"noupdate"}
+    },
+    {"appid":"bbbbbbbb",
+     "cohort":"1:33z@0.33",
+     "cohortname":"cname",
+     "updatecheck":{
+     "status":"ok",
+     "urls":{"url":[{"codebase":"http://example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='ok'.
+const char* kJSONUpdateCheckStatusOkWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"ok",
+     "actions":{"action":[{"run":"this"}]},
+     "urls":{"url":[{"codebase":"http://example.com/"},
+                    {"codebasediff":"http://diff.example.com/"}]},
+     "manifest":{
+      "version":"1.2.3.4",
+      "prodversionmin":"2.0.143.0",
+      "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='noupdate'.
+const char* kJSONUpdateCheckStatusNoUpdateWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"noupdate",
+     "actions":{"action":[{"run":"this"}]}
+     }
+    }
+   ]
+  }})";
+
+// Includes a run action for an update check with status='error'.
+const char* kJSONUpdateCheckStatusErrorWithRunAction = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"12345",
+     "updatecheck":{
+     "status":"error-osnotsupported",
+     "actions":{"action":[{"run":"this"}]}
+     }
+    }
+   ]
+  }})";
+
+// Includes four app objects with status different than 'ok'.
+const char* kJSONAppsStatusError = R"()]}'
+  {"response":{
+   "protocol":"3.1",
+   "app":[
+    {"appid":"aaaaaaaa",
+     "status":"error-unknownApplication",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"bbbbbbbb",
+     "status":"restricted",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"cccccccc",
+     "status":"error-invalidAppId",
+     "updatecheck":{"status":"error-internal"}
+    },
+    {"appid":"dddddddd",
+     "status":"foobar",
+     "updatecheck":{"status":"error-internal"}
+    }
+   ]
+  }})";
+
+TEST(UpdateClientProtocolParserJSONTest, Parse) {
+  const auto parser = std::make_unique<ProtocolParserJSON>();
+
+  // Test parsing of a number of invalid JSON cases
+  EXPECT_FALSE(parser->Parse(std::string()));
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONMissingAppId));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidCodebase));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONMissingVersion));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidVersion));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidMissingCodebase));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  EXPECT_TRUE(parser->Parse(kJSONInvalidMissingManifest));
+  EXPECT_TRUE(parser->results().list.empty());
+  EXPECT_FALSE(parser->errors().empty());
+
+  {
+    // Parse some valid XML, and check that all params came out as expected.
+    EXPECT_TRUE(parser->Parse(kJSONValid));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(1u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("ok", first_result->status.c_str());
+    EXPECT_EQ(1u, first_result->crx_urls.size());
+    EXPECT_EQ(GURL("http://example.com/"), first_result->crx_urls[0]);
+    EXPECT_EQ(GURL("http://diff.example.com/"), first_result->crx_diffurls[0]);
+    EXPECT_EQ("1.2.3.4", first_result->manifest.version);
+    EXPECT_EQ("2.0.143.0", first_result->manifest.browser_min_version);
+    EXPECT_EQ(1u, first_result->manifest.packages.size());
+    EXPECT_EQ("extension_1_2_3_4.crx", first_result->manifest.packages[0].name);
+  }
+  {
+    // Parse xml with hash value.
+    EXPECT_TRUE(parser->Parse(kJSONHash));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_FALSE(first_result->manifest.packages.empty());
+    EXPECT_EQ("1234", first_result->manifest.packages[0].hash_sha256);
+    EXPECT_EQ("5678", first_result->manifest.packages[0].hashdiff_sha256);
+  }
+  {
+    // Parse xml with package size value.
+    EXPECT_TRUE(parser->Parse(kJSONInvalidSizes));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_FALSE(first_result->manifest.packages.empty());
+    EXPECT_EQ(1234, first_result->manifest.packages[0].size);
+    EXPECT_EQ(-1234, first_result->manifest.packages[1].size);
+    EXPECT_EQ(0, first_result->manifest.packages[2].size);
+    EXPECT_EQ(0, first_result->manifest.packages[3].size);
+    EXPECT_EQ(0, first_result->manifest.packages[4].size);
+    EXPECT_EQ(0, first_result->manifest.packages[5].size);
+  }
+  {
+    // Parse xml with a <daystart> element.
+    EXPECT_TRUE(parser->Parse(kJSONWithDaystart));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    EXPECT_EQ(parser->results().daystart_elapsed_seconds, 456);
+  }
+  {
+    // Parse a no-update response.
+    EXPECT_TRUE(parser->Parse(kJSONNoUpdate));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("noupdate", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_EQ(first_result->manifest.version, "");
+  }
+  {
+    // Parse xml with one error and one success <app> tag.
+    EXPECT_TRUE(parser->Parse(kJSONTwoAppsOneError));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(2u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+    EXPECT_TRUE(first_result->manifest.version.empty());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_STREQ("ok", second_result->status.c_str());
+    EXPECT_EQ("1.2.3.4", second_result->manifest.version);
+  }
+  {
+    // Parse xml with two apps setting the cohort info.
+    EXPECT_TRUE(parser->Parse(kJSONTwoAppsSetCohort));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_EQ(2u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_NE(first_result->cohort_attrs.find("cohort"),
+              first_result->cohort_attrs.end());
+    EXPECT_EQ(first_result->cohort_attrs.find("cohort")->second, "1:2q3/");
+    EXPECT_EQ(first_result->cohort_attrs.find("cohortname"),
+              first_result->cohort_attrs.end());
+    EXPECT_EQ(first_result->cohort_attrs.find("cohorthint"),
+              first_result->cohort_attrs.end());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_NE(second_result->cohort_attrs.find("cohort"),
+              second_result->cohort_attrs.end());
+    EXPECT_EQ(second_result->cohort_attrs.find("cohort")->second, "1:33z@0.33");
+    EXPECT_NE(second_result->cohort_attrs.find("cohortname"),
+              second_result->cohort_attrs.end());
+    EXPECT_EQ(second_result->cohort_attrs.find("cohortname")->second, "cname");
+    EXPECT_EQ(second_result->cohort_attrs.find("cohorthint"),
+              second_result->cohort_attrs.end());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusOkWithRunAction));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("ok", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_STREQ("this", first_result->action_run.c_str());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusNoUpdateWithRunAction));
+    EXPECT_TRUE(parser->errors().empty());
+    EXPECT_FALSE(parser->results().list.empty());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_STREQ("noupdate", first_result->status.c_str());
+    EXPECT_EQ(first_result->extension_id, "12345");
+    EXPECT_STREQ("this", first_result->action_run.c_str());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusErrorWithRunAction));
+    EXPECT_FALSE(parser->errors().empty());
+    EXPECT_TRUE(parser->results().list.empty());
+  }
+  {
+    EXPECT_TRUE(parser->Parse(kJSONAppsStatusError));
+    EXPECT_STREQ("Unknown app status", parser->errors().c_str());
+    EXPECT_EQ(3u, parser->results().list.size());
+    const auto* first_result = &parser->results().list[0];
+    EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+    EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+    EXPECT_TRUE(first_result->manifest.version.empty());
+    const auto* second_result = &parser->results().list[1];
+    EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+    EXPECT_STREQ("restricted", second_result->status.c_str());
+    EXPECT_TRUE(second_result->manifest.version.empty());
+    const auto* third_result = &parser->results().list[2];
+    EXPECT_EQ(third_result->extension_id, "cccccccc");
+    EXPECT_STREQ("error-invalidAppId", third_result->status.c_str());
+    EXPECT_TRUE(third_result->manifest.version.empty());
+  }
+}
+
+}  // namespace update_client
diff --git a/components/update_client/protocol_serializer.cc b/components/update_client/protocol_serializer.cc
index 4eb7580..67e67ed 100644
--- a/components/update_client/protocol_serializer.cc
+++ b/components/update_client/protocol_serializer.cc
@@ -18,7 +18,6 @@
 #include "build/build_config.h"
 #include "components/update_client/activity_data_service.h"
 #include "components/update_client/persisted_data.h"
-#include "components/update_client/protocol_serializer_xml.h"
 #include "components/update_client/update_query_params.h"
 #include "components/update_client/updater_state.h"
 
@@ -57,10 +56,6 @@
 
 }  // namespace
 
-std::unique_ptr<ProtocolSerializer> ProtocolSerializer::Create() {
-  return std::make_unique<ProtocolSerializerXml>();
-}
-
 base::flat_map<std::string, std::string> BuildUpdateCheckExtraRequestHeaders(
     const std::string& prod_id,
     const base::Version& browser_version,
diff --git a/components/update_client/protocol_serializer.h b/components/update_client/protocol_serializer.h
index 066751b6..47c212a4 100644
--- a/components/update_client/protocol_serializer.h
+++ b/components/update_client/protocol_serializer.h
@@ -71,7 +71,6 @@
 
 class ProtocolSerializer {
  public:
-  static std::unique_ptr<ProtocolSerializer> Create();
   virtual ~ProtocolSerializer() = default;
   virtual std::string Serialize(
       const protocol_request::Request& request) const = 0;
diff --git a/components/update_client/protocol_serializer_json.cc b/components/update_client/protocol_serializer_json.cc
new file mode 100644
index 0000000..b94d2c05
--- /dev/null
+++ b/components/update_client/protocol_serializer_json.cc
@@ -0,0 +1,183 @@
+// 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 "components/update_client/protocol_serializer_json.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+#include "components/update_client/updater_state.h"
+
+namespace update_client {
+
+using Value = base::Value;
+
+std::string ProtocolSerializerJSON::Serialize(
+    const protocol_request::Request& request) const {
+  Value root_node(Value::Type::DICTIONARY);
+  auto* request_node =
+      root_node.SetKey("request", Value(Value::Type::DICTIONARY));
+  request_node->SetKey("protocol", Value(request.protocol_version));
+  request_node->SetKey("dedup", Value("cr"));
+  request_node->SetKey("acceptformat", Value("crx2,crx3"));
+  if (!request.additional_attributes.empty()) {
+    for (const auto& attr : request.additional_attributes)
+      request_node->SetKey(attr.first, Value(attr.second));
+  }
+  request_node->SetKey("sessionid", Value(request.session_id));
+  request_node->SetKey("requestid", Value(request.request_id));
+  request_node->SetKey("@updater", Value(request.updatername));
+  request_node->SetKey("prodversion", Value(request.updaterversion));
+  request_node->SetKey("updaterversion", Value(request.prodversion));
+  request_node->SetKey("lang", Value(request.lang));
+  request_node->SetKey("@os", Value(request.operating_system));
+  request_node->SetKey("arch", Value(request.arch));
+  request_node->SetKey("nacl_arch", Value(request.nacl_arch));
+#if defined(OS_WIN)
+  if (request.is_wow64)
+    request_node->SetKey("wow64", Value(request.is_wow64));
+#endif  // OS_WIN
+  if (!request.updaterchannel.empty())
+    request_node->SetKey("updaterchannel", Value(request.updaterchannel));
+  if (!request.prodchannel.empty())
+    request_node->SetKey("prodchannel", Value(request.prodchannel));
+  if (!request.dlpref.empty())
+    request_node->SetKey("dlpref", Value(request.dlpref));
+  if (request.domain_joined) {
+    request_node->SetKey(UpdaterState::kIsEnterpriseManaged,
+                         Value(*request.domain_joined));
+  }
+
+  // HW platform information.
+  auto* hw_node = request_node->SetKey("hw", Value(Value::Type::DICTIONARY));
+  hw_node->SetKey("physmemory", Value(static_cast<int>(request.hw.physmemory)));
+
+  // OS version and platform information.
+  auto* os_node = request_node->SetKey("os", Value(Value::Type::DICTIONARY));
+  os_node->SetKey("platform", Value(request.os.platform));
+  os_node->SetKey("arch", Value(request.os.arch));
+  if (!request.os.version.empty())
+    os_node->SetKey("version", Value(request.os.version));
+  if (!request.os.service_pack.empty())
+    os_node->SetKey("sp", Value(request.os.service_pack));
+
+#if defined(GOOGLE_CHROME_BUILD)
+  if (request.updater) {
+    const auto& updater = *request.updater;
+    auto* updater_node =
+        request_node->SetKey("updater", Value(Value::Type::DICTIONARY));
+    updater_node->SetKey("name", Value(updater.name));
+    updater_node->SetKey("ismachine", Value(updater.is_machine));
+    updater_node->SetKey("autoupdatecheckenabled",
+                         Value(updater.autoupdate_check_enabled));
+    updater_node->SetKey("updatepolicy", Value(updater.update_policy));
+    if (!updater.version.empty())
+      updater_node->SetKey("version", Value(updater.version));
+    if (updater.last_checked)
+      updater_node->SetKey("lastchecked", Value(*updater.last_checked));
+    if (updater.last_started)
+      updater_node->SetKey("laststarted", Value(*updater.last_started));
+  }
+#endif
+
+  std::vector<Value> app_nodes;
+  for (const auto& app : request.apps) {
+    Value app_node(Value::Type::DICTIONARY);
+    app_node.SetKey("appid", Value(app.app_id));
+    app_node.SetKey("version", Value(app.version));
+    if (!app.brand_code.empty())
+      app_node.SetKey("brand", Value(app.brand_code));
+    if (!app.install_source.empty())
+      app_node.SetKey("installsource", Value(app.install_source));
+    if (!app.install_location.empty())
+      app_node.SetKey("installedby", Value(app.install_location));
+    if (!app.cohort.empty())
+      app_node.SetKey("cohort", Value(app.cohort));
+    if (!app.cohort_name.empty())
+      app_node.SetKey("cohortname", Value(app.cohort_name));
+    if (!app.cohort_hint.empty())
+      app_node.SetKey("cohorthint", Value(app.cohort_hint));
+    if (app.enabled)
+      app_node.SetKey("enabled", Value(*app.enabled));
+
+    if (app.disabled_reasons && !app.disabled_reasons->empty()) {
+      std::vector<Value> disabled_nodes;
+      for (const int disabled_reason : *app.disabled_reasons) {
+        Value disabled_node(Value::Type::DICTIONARY);
+        disabled_node.SetKey("reason", Value(disabled_reason));
+        disabled_nodes.push_back(std::move(disabled_node));
+      }
+      app_node.SetKey("disabled", Value(disabled_nodes));
+    }
+
+    for (const auto& attr : app.installer_attributes)
+      app_node.SetKey(attr.first, Value(attr.second));
+
+    if (app.update_check) {
+      auto* update_check_node =
+          app_node.SetKey("updatecheck", Value(Value::Type::DICTIONARY));
+      if (app.update_check->is_update_disabled)
+        update_check_node->SetKey("updatedisabled", Value(true));
+    }
+
+    if (app.ping) {
+      const auto& ping = *app.ping;
+      auto* ping_node = app_node.SetKey("ping", Value(Value::Type::DICTIONARY));
+      if (!ping.ping_freshness.empty())
+        ping_node->SetKey("ping_freshness", Value(ping.ping_freshness));
+
+      // Output "ad" or "a" only if the this app has been seen 'active'.
+      if (ping.date_last_active) {
+        ping_node->SetKey("ad", Value(*ping.date_last_active));
+      } else if (ping.days_since_last_active_ping) {
+        ping_node->SetKey("a", Value(*ping.days_since_last_active_ping));
+      }
+
+      // Output "rd" if valid or "r" as a last resort roll call metric.
+      if (ping.date_last_roll_call)
+        ping_node->SetKey("rd", Value(*ping.date_last_roll_call));
+      else
+        ping_node->SetKey("r", Value(ping.days_since_last_roll_call));
+    }
+
+    if (!app.fingerprint.empty()) {
+      std::vector<Value> package_nodes;
+      Value package(Value::Type::DICTIONARY);
+      package.SetKey("fp", Value(app.fingerprint));
+      package_nodes.push_back(std::move(package));
+      auto* packages_node =
+          app_node.SetKey("packages", Value(Value::Type::DICTIONARY));
+      packages_node->SetKey("package", Value(package_nodes));
+    }
+
+    if (app.events) {
+      std::vector<Value> event_nodes;
+      for (const auto& event : *app.events) {
+        DCHECK(event.is_dict());
+        DCHECK(!event.DictEmpty());
+        event_nodes.push_back(event.Clone());
+      }
+      app_node.SetKey("event", Value(event_nodes));
+    }
+
+    app_nodes.push_back(std::move(app_node));
+  }
+
+  if (!app_nodes.empty())
+    request_node->SetKey("app", Value(std::move(app_nodes)));
+
+  std::string msg;
+  return base::JSONWriter::WriteWithOptions(
+             root_node, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
+             &msg)
+             ? msg
+             : std::string();
+}
+
+}  // namespace update_client
diff --git a/components/update_client/protocol_serializer_json.h b/components/update_client/protocol_serializer_json.h
new file mode 100644
index 0000000..71f2a99
--- /dev/null
+++ b/components/update_client/protocol_serializer_json.h
@@ -0,0 +1,28 @@
+// 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+
+#include <string>
+
+#include "components/update_client/protocol_serializer.h"
+
+namespace update_client {
+
+class ProtocolSerializerJSON final : public ProtocolSerializer {
+ public:
+  ProtocolSerializerJSON() = default;
+
+  // Overrides for ProtocolSerializer.
+  std::string Serialize(
+      const protocol_request::Request& request) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProtocolSerializerJSON);
+};
+
+}  // namespace update_client
+
+#endif  // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
diff --git a/components/update_client/protocol_serializer_json_unittest.cc b/components/update_client/protocol_serializer_json_unittest.cc
new file mode 100644
index 0000000..020cc10
--- /dev/null
+++ b/components/update_client/protocol_serializer_json_unittest.cc
@@ -0,0 +1,126 @@
+// 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 <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/protocol_serializer_json.h"
+#include "components/update_client/updater_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using base::Value;
+using std::string;
+
+namespace update_client {
+
+TEST(SerializeRequestJSON, Serialize) {
+  // When no updater state is provided, then check that the elements and
+  // attributes related to the updater state are not serialized.
+
+  std::vector<base::Value> events;
+  events.push_back(Value(Value::Type::DICTIONARY));
+  events.push_back(Value(Value::Type::DICTIONARY));
+  events[0].SetKey("a", Value(1));
+  events[0].SetKey("b", Value("2"));
+  events[1].SetKey("error", Value(0));
+
+  auto pref = std::make_unique<TestingPrefServiceSimple>();
+  PersistedData::RegisterPrefs(pref->registry());
+  auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+  std::vector<std::string> items = {"id1"};
+  metadata->SetDateLastRollCall(items, 1234);
+
+  std::vector<protocol_request::App> apps;
+  apps.push_back(MakeProtocolApp(
+      "id1", base::Version("1.0"), "brand1", "source1", "location1", "fp1",
+      {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1", "cn1", {0, 1},
+      MakeProtocolUpdateCheck(true), MakeProtocolPing("id1", metadata.get())));
+  apps.push_back(
+      MakeProtocolApp("id2", base::Version("2.0"), std::move(events)));
+
+  const auto request = std::make_unique<ProtocolSerializerJSON>()->Serialize(
+      MakeProtocolRequest("15160585-8ADE-4D3C-839B-1281A6035D1F", "prod_id",
+                          "1.0", "lang", "channel", "OS", "cacheable",
+                          {{"extra", "params"}}, nullptr, std::move(apps)));
+  constexpr char regex[] =
+      R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+      R"("acceptformat":"crx2,crx3",)"
+      R"("app":\[{"appid":"id1","attr1":"1","attr2":"2","brand":"brand1",)"
+      R"("cohort":"c1","cohorthint":"ch1","cohortname":"cn1",)"
+      R"("disabled":\[{"reason":0},{"reason":1}],"enabled":false,)"
+      R"("installedby":"location1","installsource":"source1",)"
+      R"("packages":{"package":\[{"fp":"fp1"}]},)"
+      R"("ping":{"ping_freshness":"{[-\w]{36}}","rd":1234},)"
+      R"("updatecheck":{"updatedisabled":true},"version":"1.0"},)"
+      R"({"appid":"id2","event":\[{"a":1,"b":"2"},{"error":0}],)"
+      R"("version":"2.0"}],"arch":"\w+","dedup":"cr","dlpref":"cacheable",)"
+      R"("extra":"params","hw":{"physmemory":\d+},"lang":"lang",)"
+      R"("nacl_arch":"[-\w]+","os":{"arch":"[_,-.\w]+","platform":"OS",)"
+      R"(("sp":"[\s\w]+",)?"version":"[-.\w]+"},"prodchannel":"channel",)"
+      R"("prodversion":"1.0","protocol":"3.1","requestid":"{[-\w]{36}}",)"
+      R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
+      R"("updaterversion":"1.0"(,"wow64":true)?}})";
+  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+TEST(SerializeRequestJSON, DownloadPreference) {
+  // Verifies that an empty |download_preference| is not serialized.
+  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+  auto request = serializer->Serialize(
+      MakeProtocolRequest("15160585-8ADE-4D3C-839B-1281A6035D1F", "", "", "",
+                          "", "", "", {}, nullptr, {}));
+  EXPECT_FALSE(RE2::PartialMatch(request, R"("dlpref":)")) << request;
+
+  // Verifies that |download_preference| is serialized.
+  request = serializer->Serialize(
+      MakeProtocolRequest("15160585-8ADE-4D3C-839B-1281A6035D1F", "", "", "",
+                          "", "", "cacheable", {}, nullptr, {}));
+  EXPECT_TRUE(RE2::PartialMatch(request, R"("dlpref":"cacheable")")) << request;
+}
+
+// When present, updater state attributes are only serialized for Google builds,
+// except the |domainjoined| attribute, which is serialized in all cases.
+TEST(SerializeRequestJSON, UpdaterStateAttributes) {
+  const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+  UpdaterState::Attributes attributes;
+  attributes["ismachine"] = "1";
+  attributes["domainjoined"] = "1";
+  attributes["name"] = "Omaha";
+  attributes["version"] = "1.2.3.4";
+  attributes["laststarted"] = "1";
+  attributes["lastchecked"] = "2";
+  attributes["autoupdatecheckenabled"] = "0";
+  attributes["updatepolicy"] = "-1";
+  const auto request = serializer->Serialize(MakeProtocolRequest(
+      "15160585-8ADE-4D3C-839B-1281A6035D1F", "prod_id", "1.0", "lang",
+      "channel", "OS", "cacheable", {{"extra", "params"}}, &attributes, {}));
+  constexpr char regex[] =
+      R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+      R"("acceptformat":"crx2,crx3","arch":"\w+","dedup":"cr",)"
+      R"("dlpref":"cacheable","domainjoined":true,"extra":"params",)"
+      R"("hw":{"physmemory":\d+},"lang":"lang","nacl_arch":"[-\w]+",)"
+      R"("os":{"arch":"[,-.\w]+","platform":"OS",("sp":"[\s\w]+",)?)"
+      R"("version":"[-.\w]+"},"prodchannel":"channel","prodversion":"1.0",)"
+      R"("protocol":"3.1","requestid":"{[-\w]{36}}","sessionid":"{[-\w]{36}}",)"
+#if defined(GOOGLE_CHROME_BUILD)
+      R"("updater":{"autoupdatecheckenabled":false,"ismachine":true,)"
+      R"("lastchecked":2,"laststarted":1,"name":"Omaha","updatepolicy":-1,)"
+      R"("version":"1\.2\.3\.4"},)"
+#endif  // GOOGLE_CHROME_BUILD
+      R"("updaterchannel":"channel","updaterversion":"1.0"(,"wow64":true)?}})";
+  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+}  // namespace update_client
diff --git a/components/update_client/protocol_serializer_xml.cc b/components/update_client/protocol_serializer_xml.cc
index af0bbc6..5cf61e4c 100644
--- a/components/update_client/protocol_serializer_xml.cc
+++ b/components/update_client/protocol_serializer_xml.cc
@@ -181,6 +181,8 @@
             base::StringAppendF(&msg, "\"%s\"", value.GetString().c_str());
           else if (value.is_int())
             base::StringAppendF(&msg, "\"%d\"", value.GetInt());
+          else if (value.is_double())
+            base::StringAppendF(&msg, "\"%.0f\"", value.GetDouble());
           else
             NOTREACHED();
         }
diff --git a/components/update_client/protocol_serializer_xml_unittest.cc b/components/update_client/protocol_serializer_xml_unittest.cc
index 60bdd3c..9cc8b70 100644
--- a/components/update_client/protocol_serializer_xml_unittest.cc
+++ b/components/update_client/protocol_serializer_xml_unittest.cc
@@ -1,10 +1,12 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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 <memory>
 #include <string>
 
 #include "components/update_client/protocol_serializer.h"
+#include "components/update_client/protocol_serializer_xml.h"
 #include "components/update_client/updater_state.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/re2/src/re2/re2.h"
@@ -13,11 +15,11 @@
 
 namespace update_client {
 
-TEST(SerializeRequest, Serialize) {
+TEST(SerializeRequestXml, Serialize) {
   // When no updater state is provided, then check that the elements and
   // attributes related to the updater state are not serialized.
   const auto request =
-      ProtocolSerializer::Create()->Serialize(MakeProtocolRequest(
+      std::make_unique<ProtocolSerializerXml>()->Serialize(MakeProtocolRequest(
           "15160585-8ADE-4D3C-839B-1281A6035D1F", "prod_id", "1.0", "lang",
           "channel", "OS", "cacheable", {{"extra", "params"}}, nullptr, {}));
   constexpr char regex[] =
@@ -28,15 +30,15 @@
       R"(updater="prod_id" updaterversion="1\.0" prodversion="1\.0" )"
       R"(lang="lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" (wow64="1" )?)"
       R"(updaterchannel="channel" prodchannel="channel" dlpref="cacheable">)"
-      R"(<hw physmemory="[0-9]+"/>)"
+      R"(<hw physmemory="\d+"/>)"
       R"(<os platform="OS" arch="[,-.\w]+" version="[-.\w]+"( sp="[\s\w]+")?/>)"
       R"(</request>)";
   EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
 }
 
-TEST(BuildProtocolRequest, DownloadPreference) {
+TEST(SerializeRequestXml, DownloadPreference) {
   // Verifies that an empty |download_preference| is not serialized.
-  const auto serializer = ProtocolSerializer::Create();
+  const auto serializer = std::make_unique<ProtocolSerializerXml>();
   auto request = serializer->Serialize(
       MakeProtocolRequest("15160585-8ADE-4D3C-839B-1281A6035D1F", "", "", "",
                           "", "", "", {}, nullptr, {}));
@@ -51,8 +53,8 @@
 
 // When present, updater state attributes are only serialized for Google builds,
 // except the |domainjoined| attribute, which is serialized in all cases.
-TEST(BuildProtocolRequest, UpdaterStateAttributes) {
-  const auto serializer = ProtocolSerializer::Create();
+TEST(SerializeRequestXml, UpdaterStateAttributes) {
+  const auto serializer = std::make_unique<ProtocolSerializerXml>();
   UpdaterState::Attributes attributes;
   attributes["ismachine"] = "1";
   attributes["domainjoined"] = "1";
@@ -74,7 +76,7 @@
       R"(lang="lang" os="\w+" arch="\w+" nacl_arch="[-\w]+" (wow64="1" )?)"
       R"(updaterchannel="channel" prodchannel="channel" dlpref="cacheable" )"
       R"(domainjoined="1">)"
-      R"(<hw physmemory="[0-9]+"/>)"
+      R"(<hw physmemory="\d+"/>)"
       R"(<os platform="OS" arch="[,-.\w]+" version="[-.\w]+"( sp="[\s\w]+")?/>)"
 #if defined(GOOGLE_CHROME_BUILD)
       R"(<updater name="Omaha" version="1\.2\.3\.4" lastchecked="2" )"
diff --git a/components/update_client/test_configurator.cc b/components/update_client/test_configurator.cc
index 87e5596..0c04089 100644
--- a/components/update_client/test_configurator.cc
+++ b/components/update_client/test_configurator.cc
@@ -38,6 +38,7 @@
       ondemand_time_(0),
       enabled_cup_signing_(false),
       enabled_component_updates_(true),
+      use_JSON_(false),
       test_shared_loader_factory_(
           base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
               &test_url_loader_factory_)) {
@@ -182,6 +183,10 @@
   app_guid_ = app_guid;
 }
 
+void TestConfigurator::SetUseJSON(bool use_JSON) {
+  use_JSON_ = use_JSON;
+}
+
 PrefService* TestConfigurator::GetPrefService() const {
   return nullptr;
 }
@@ -204,6 +209,8 @@
 
 std::unique_ptr<ProtocolHandlerFactory>
 TestConfigurator::GetProtocolHandlerFactory() const {
+  if (use_JSON_)
+    return std::make_unique<ProtocolHandlerFactoryJSON>();
   return std::make_unique<ProtocolHandlerFactoryXml>();
 }
 
diff --git a/components/update_client/test_configurator.h b/components/update_client/test_configurator.h
index 6769892..7da9e28 100644
--- a/components/update_client/test_configurator.h
+++ b/components/update_client/test_configurator.h
@@ -113,6 +113,7 @@
   void SetUpdateCheckUrl(const GURL& url);
   void SetPingUrl(const GURL& url);
   void SetAppGuid(const std::string& app_guid);
+  void SetUseJSON(bool use_JSON);
   network::TestURLLoaderFactory* test_url_loader_factory() {
     return &test_url_loader_factory_;
   }
@@ -132,6 +133,7 @@
   GURL update_check_url_;
   GURL ping_url_;
   std::string app_guid_;
+  bool use_JSON_;
 
   std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
   std::unique_ptr<service_manager::Connector> connector_;
diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc
index d98955b3..ab65a9c 100644
--- a/components/update_client/update_checker_unittest.cc
+++ b/components/update_client/update_checker_unittest.cc
@@ -6,11 +6,13 @@
 
 #include <map>
 #include <memory>
+#include <tuple>
 #include <utility>
 #include <vector>
 
 #include "base/bind.h"
 #include "base/files/file_util.h"
+#include "base/json/json_reader.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
@@ -109,8 +111,10 @@
 
 }  // namespace
 
-class UpdateCheckerTest : public testing::Test,
-                          public testing::WithParamInterface<bool> {
+using UpdateCheckTestParams =
+    std::tuple<bool /*is_foreground*/, bool /*use_JSON*/>;
+
+class UpdateCheckerTest : public testing::TestWithParam<UpdateCheckTestParams> {
  public:
   UpdateCheckerTest();
   ~UpdateCheckerTest() override;
@@ -147,6 +151,9 @@
 
   scoped_refptr<UpdateContext> update_context_;
 
+  bool use_JSON_ = false;
+  bool is_foreground_ = false;
+
  private:
   scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
 
@@ -156,7 +163,10 @@
   DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest);
 };
 
-INSTANTIATE_TEST_CASE_P(IsForeground, UpdateCheckerTest, testing::Bool());
+// This test is parameterized for |is_foreground and |use_JSON|.
+INSTANTIATE_TEST_CASE_P(Parameterized,
+                        UpdateCheckerTest,
+                        testing::Combine(testing::Bool(), testing::Bool()));
 
 UpdateCheckerTest::UpdateCheckerTest()
     : scoped_task_environment_(
@@ -166,7 +176,11 @@
 }
 
 void UpdateCheckerTest::SetUp() {
+  std::tie(is_foreground_, use_JSON_) = GetParam();
+
   config_ = base::MakeRefCounted<TestConfigurator>();
+  config_->SetUseJSON(use_JSON_);
+
   pref_ = std::make_unique<TestingPrefServiceSimple>();
   activity_data_service_ = std::make_unique<ActivityDataServiceTest>();
   PersistedData::RegisterPrefs(pref_->registry());
@@ -182,6 +196,7 @@
   error_ = 0;
   retry_after_sec_ = 0;
   update_context_ = MakeMockUpdateContext();
+  update_context_->is_foreground = is_foreground_;
 }
 
 void UpdateCheckerTest::TearDown() {
@@ -245,10 +260,10 @@
 TEST_P(UpdateCheckerTest, UpdateCheckSuccess) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                          : "updatecheck_reply_1.xml")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  update_context_->is_foreground = GetParam();
 
   IdToComponentPtrMap components;
   components[kUpdateItemId] = MakeComponent();
@@ -270,30 +285,102 @@
 
   // Sanity check the request.
   const auto& request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(request, testing::HasSubstr(
-                           R"(request protocol="3.1" dedup="cr" )"
-                           R"(acceptformat="crx2,crx3" extra="params" )"
-                           R"(testrequest="1")"));
-  // The request must not contain any "dlpref" in the default case.
-  EXPECT_THAT(request, testing::Not(testing::HasSubstr(R"( dlpref=")")));
-  EXPECT_THAT(request,
-              testing::HasSubstr(
-                  std::string(R"(<app appid=")") + kUpdateItemId +
-                  R"(" version="0.9" brand="TEST")" +
-                  (GetParam() ? R"( installsource="ondemand")" : "") +
-                  R"( ap="some_ap" enabled="1"><updatecheck/><ping r="-2"/>)"));
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(R"(<packages><package fp="fp1"/></packages></app>)"));
-  EXPECT_THAT(request, testing::HasSubstr("<hw physmemory="));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    const auto* request = root->FindKey("request");
+    ASSERT_TRUE(request);
+    EXPECT_TRUE(request->FindKey("@os"));
+    EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+    EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+    EXPECT_TRUE(request->FindKey("arch"));
+    EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+    EXPECT_EQ("params", request->FindKey("extra")->GetString());
+    EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+    EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+    EXPECT_TRUE(request->FindKey("nacl_arch"));
+    EXPECT_EQ("fake_channel_string",
+              request->FindKey("prodchannel")->GetString());
+    EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+    EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+    EXPECT_TRUE(request->FindKey("requestid"));
+    EXPECT_TRUE(request->FindKey("sessionid"));
+    EXPECT_EQ("1", request->FindKey("testrequest")->GetString());
+    EXPECT_EQ("fake_channel_string",
+              request->FindKey("updaterchannel")->GetString());
+    EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
 
-  // Tests that the product id is injected correctly from the configurator.
-  EXPECT_THAT(request, testing::HasSubstr(
-                           R"( updater="fake_prodid" updaterversion="30.0")"
-                           R"( prodversion="30.0" )"));
+    // No "dlpref" is sent by default.
+    EXPECT_FALSE(request->FindKey("dlpref"));
 
-  // Tests that there is a sessionid attribute.
-  EXPECT_THAT(request, testing::HasSubstr(" sessionid="));
+    EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+    EXPECT_EQ("Fake Operating System",
+              request->FindPath({"os", "platform"})->GetString());
+    EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+    const auto& app = request->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+    if (is_foreground_)
+      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+    EXPECT_EQ("some_ap", app.FindKey("ap")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck"));
+    EXPECT_TRUE(app.FindKey("ping"));
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                         ->GetList()[0]
+                         .FindKey("fp")
+                         ->GetString());
+
+#if (OS_WIN)
+    EXPECT_TRUE(request->FindKey("domainjoined"));
+#if defined(GOOGLE_CHROME_BUILD)
+    const auto* updater = request->FindKey("updater");
+    EXPECT_TRUE(updater);
+    EXPECT_EQ("Omaha", updater->FindKey("name")->GetString());
+    EXPECT_TRUE(updater->FindKey("autoupdatecheckenabled")->is_bool());
+    EXPECT_TRUE(updater->FindKey("ismachine")->is_bool());
+    EXPECT_TRUE(updater->FindKey("lastchecked")->is_int());
+    EXPECT_TRUE(updater->FindKey("laststarted")->is_int());
+    EXPECT_TRUE(updater->FindKey("updatepolicy")->is_int());
+    EXPECT_TRUE(updater->FindKey("version")->is_string());
+#endif  // GOOGLE_CHROME_BUILD
+#endif  // OS_WINDOWS
+  } else {
+    EXPECT_THAT(request, testing::HasSubstr(
+                             R"(request protocol="3.1" dedup="cr" )"
+                             R"(acceptformat="crx2,crx3" extra="params" )"
+                             R"(testrequest="1")"));
+    // The request must not contain any "dlpref" in the default case.
+    EXPECT_THAT(request, testing::Not(testing::HasSubstr(R"( dlpref=")")));
+    EXPECT_THAT(
+        request,
+        testing::HasSubstr(
+            std::string(R"(<app appid=")") + kUpdateItemId +
+            R"(" version="0.9" brand="TEST")" +
+            (is_foreground_ ? R"( installsource="ondemand")" : "") +
+            R"( ap="some_ap" enabled="1"><updatecheck/><ping r="-2"/>)"));
+    EXPECT_THAT(request,
+                testing::HasSubstr(
+                    R"(<packages><package fp="fp1"/></packages></app>)"));
+    EXPECT_THAT(request, testing::HasSubstr("<hw physmemory="));
+
+    // Tests that the product id is injected correctly from the configurator.
+    EXPECT_THAT(request, testing::HasSubstr(
+                             R"( updater="fake_prodid" updaterversion="30.0")"
+                             R"( prodversion="30.0" )"));
+    // Tests that there is a sessionid attribute.
+    EXPECT_THAT(request, testing::HasSubstr(" sessionid="));
+#if (OS_WIN)
+    EXPECT_THAT(request, testing::HasSubstr(" domainjoined="));
+#if defined(GOOGLE_CHROME_BUILD)
+    // Check the Omaha updater state data in the request.
+    EXPECT_THAT(request, testing::HasSubstr("<updater "));
+    EXPECT_THAT(request, testing::HasSubstr(R"( name="Omaha" )"));
+#endif  // GOOGLE_CHROME_BUILD
+#endif  // OS_WINDOWS
+  }
 
   // Sanity check the arguments of the callback after parsing.
   EXPECT_EQ(ErrorCategory::kNone, error_category_);
@@ -311,22 +398,13 @@
   EXPECT_EQ(GURL("http://localhost/download/"), result.crx_urls.front());
   EXPECT_STREQ("this", result.action_run.c_str());
 
-#if (OS_WIN)
-  EXPECT_THAT(request, testing::HasSubstr(" domainjoined="));
-#if defined(GOOGLE_CHROME_BUILD)
-  // Check the Omaha updater state data in the request.
-  EXPECT_THAT(request, testing::HasSubstr("<updater "));
-  EXPECT_THAT(request, testing::HasSubstr(R"( name="Omaha" )"));
-#endif  // GOOGLE_CHROME_BUILD
-#endif  // OS_WINDOWS
-
   // Check the DDOS protection header values.
   const auto extra_request_headers =
       std::get<1>(post_interceptor_->GetRequests()[0]);
   EXPECT_TRUE(extra_request_headers.HasHeader("X-Goog-Update-Interactivity"));
   std::string header;
   extra_request_headers.GetHeader("X-Goog-Update-Interactivity", &header);
-  EXPECT_STREQ(GetParam() ? "fg" : "bg", header.c_str());
+  EXPECT_STREQ(is_foreground_ ? "fg" : "bg", header.c_str());
   extra_request_headers.GetHeader("X-Goog-Update-Updater", &header);
   EXPECT_STREQ("fake_prodid-30.0", header.c_str());
   extra_request_headers.GetHeader("X-Goog-Update-AppId", &header);
@@ -334,10 +412,11 @@
 }
 
 // Tests that an invalid "ap" is not serialized.
-TEST_F(UpdateCheckerTest, UpdateCheckInvalidAp) {
+TEST_P(UpdateCheckerTest, UpdateCheckInvalidAp) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                          : "updatecheck_reply_1.xml")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -356,19 +435,47 @@
   RunThreads();
 
   const auto request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(request, testing::HasSubstr(
-                           std::string(R"(app appid=")") + kUpdateItemId +
-                           R"(" version="0.9" brand="TEST" enabled="1">)" +
-                           R"(<updatecheck/><ping r="-2"/>)"));
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(R"(<packages><package fp="fp1"/></packages></app>)"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+    if (is_foreground_)
+      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+    EXPECT_FALSE(app.FindKey("ap"));
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck"));
+    EXPECT_TRUE(app.FindKey("ping"));
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                         ->GetList()[0]
+                         .FindKey("fp")
+                         ->GetString());
+  } else {
+    if (is_foreground_) {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" brand="TEST" )"
+                               R"(installsource="ondemand" enabled="1">)"
+                               R"(<updatecheck/><ping r="-2"/>)"));
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" brand="TEST" enabled="1">)"
+                               R"(<updatecheck/><ping r="-2"/>)"));
+    }
+    EXPECT_THAT(request,
+                testing::HasSubstr(
+                    R"(<packages><package fp="fp1"/></packages></app>)"));
+  }
 }
 
-TEST_F(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
+TEST_P(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                          : "updatecheck_reply_1.xml")));
 
   config_->SetBrand("TOOLONG");   // Sets an invalid brand code.
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
@@ -384,18 +491,43 @@
   RunThreads();
 
   const auto request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(
-          std::string(R"(<app appid=")") + kUpdateItemId +
-          R"(" version="0.9" enabled="1"><updatecheck/><ping r="-2"/>)"));
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(R"(<packages><package fp="fp1"/></packages></app>)"));
+
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_FALSE(app.FindKey("brand"));
+    if (is_foreground_)
+      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck"));
+    EXPECT_TRUE(app.FindKey("ping"));
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                         ->GetList()[0]
+                         .FindKey("fp")
+                         ->GetString());
+  } else {
+    if (is_foreground_) {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" installsource="ondemand" )"
+                               R"(enabled="1"><updatecheck/><ping r="-2"/>)"));
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" enabled="1">)" +
+                               R"(<updatecheck/><ping r="-2"/>)"));
+    }
+    EXPECT_THAT(request,
+                testing::HasSubstr(
+                    R"(<packages><package fp="fp1"/></packages></app>)"));
+  }
 }
 
 // Simulates a 403 server response error.
-TEST_F(UpdateCheckerTest, UpdateCheckError) {
+TEST_P(UpdateCheckerTest, UpdateCheckError) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"), net::HTTP_FORBIDDEN));
 
@@ -420,10 +552,11 @@
   EXPECT_FALSE(results_);
 }
 
-TEST_F(UpdateCheckerTest, UpdateCheckDownloadPreference) {
+TEST_P(UpdateCheckerTest, UpdateCheckDownloadPreference) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                          : "updatecheck_reply_1.xml")));
 
   config_->SetDownloadPreference(string("cacheable"));
 
@@ -441,16 +574,23 @@
 
   // The request must contain dlpref="cacheable".
   const auto request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(request, testing::HasSubstr(R"( dlpref="cacheable")"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    EXPECT_EQ("cacheable",
+              root->FindKey("request")->FindKey("dlpref")->GetString());
+  } else {
+    EXPECT_THAT(request, testing::HasSubstr(R"( dlpref="cacheable")"));
+  }
 }
 
 // This test is checking that an update check signed with CUP fails, since there
 // is currently no entity that can respond with a valid signed response.
 // A proper CUP test requires network mocks, which are not available now.
-TEST_F(UpdateCheckerTest, UpdateCheckCupError) {
+TEST_P(UpdateCheckerTest, UpdateCheckCupError) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                          : "updatecheck_reply_1.xml")));
 
   config_->SetEnabledCupSigning(true);
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
@@ -472,13 +612,40 @@
 
   // Sanity check the request.
   const auto& request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(request, testing::HasSubstr(
-                           std::string(R"(<app appid=")") + kUpdateItemId +
-                           R"(" version="0.9" brand="TEST" enabled="1">)" +
-                           R"(<updatecheck/><ping r="-2"/>)"));
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(R"(<packages><package fp="fp1"/></packages></app>)"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+    if (is_foreground_)
+      EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck"));
+    EXPECT_TRUE(app.FindKey("ping"));
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+                         ->GetList()[0]
+                         .FindKey("fp")
+                         ->GetString());
+
+  } else {
+    if (is_foreground_) {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(<app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" brand="TEST" )"
+                               R"(installsource="ondemand" enabled="1">)"
+                               R"(<updatecheck/><ping r="-2"/>)"));
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(<app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" brand="TEST" enabled="1">)"
+                               R"(<updatecheck/><ping r="-2"/>)"));
+    }
+    EXPECT_THAT(request,
+                testing::HasSubstr(
+                    R"(<packages><package fp="fp1"/></packages></app>)"));
+  }
 
   // Expect an error since the response is not trusted.
   EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
@@ -488,7 +655,7 @@
 
 // Tests that the UpdateCheckers will not make an update check for a
 // component that requires encryption when the update check URL is unsecure.
-TEST_F(UpdateCheckerTest, UpdateCheckRequiresEncryptionError) {
+TEST_P(UpdateCheckerTest, UpdateCheckRequiresEncryptionError) {
   config_->SetUpdateCheckUrl(GURL("http:\\foo\bar"));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
@@ -512,13 +679,13 @@
 
 // Tests that the PersistedData will get correctly update and reserialize
 // the elapsed_days value.
-TEST_F(UpdateCheckerTest, UpdateCheckLastRollCall) {
+TEST_P(UpdateCheckerTest, UpdateCheckLastRollCall) {
+  const char* filename =
+      use_JSON_ ? "updatecheck_reply_4.json" : "updatecheck_reply_4.xml";
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_4.xml")));
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_4.xml")));
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -546,22 +713,34 @@
       << post_interceptor_->GetRequestsAsString();
   ASSERT_EQ(2, post_interceptor_->GetCount())
       << post_interceptor_->GetRequestsAsString();
-  EXPECT_THAT(post_interceptor_->GetRequestBody(0),
-              testing::HasSubstr(R"(<ping r="5")"));
-  EXPECT_THAT(post_interceptor_->GetRequestBody(1),
-              testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
+
+  if (use_JSON_) {
+    const auto root1 =
+        base::JSONReader().Read(post_interceptor_->GetRequestBody(0));
+    const auto& app1 = root1->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(5, app1.FindPath({"ping", "r"})->GetInt());
+    const auto root2 =
+        base::JSONReader().Read(post_interceptor_->GetRequestBody(1));
+    const auto& app2 = root2->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(3383, app2.FindPath({"ping", "rd"})->GetInt());
+    EXPECT_TRUE(app2.FindPath({"ping", "ping_freshness"})->is_string());
+  } else {
+    EXPECT_THAT(post_interceptor_->GetRequestBody(0),
+                testing::HasSubstr(R"(<ping r="5")"));
+    EXPECT_THAT(post_interceptor_->GetRequestBody(1),
+                testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
+  }
 }
 
-TEST_F(UpdateCheckerTest, UpdateCheckLastActive) {
+TEST_P(UpdateCheckerTest, UpdateCheckLastActive) {
+  const char* filename =
+      use_JSON_ ? "updatecheck_reply_4.json" : "updatecheck_reply_4.xml";
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_4.xml")));
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_4.xml")));
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_4.xml")));
+      std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -606,16 +785,42 @@
       << post_interceptor_->GetRequestsAsString();
   ASSERT_EQ(3, post_interceptor_->GetCount())
       << post_interceptor_->GetRequestsAsString();
-  EXPECT_THAT(post_interceptor_->GetRequestBody(0),
-              testing::HasSubstr(R"(<ping a="10" r="-2"/>)"));
-  EXPECT_THAT(
-      post_interceptor_->GetRequestBody(1),
-      testing::HasSubstr(R"(<ping ad="3383" rd="3383" ping_freshness=)"));
-  EXPECT_THAT(post_interceptor_->GetRequestBody(2),
-              testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
+
+  if (use_JSON_) {
+    {
+      const auto root =
+          base::JSONReader().Read(post_interceptor_->GetRequestBody(0));
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(10, app.FindPath({"ping", "a"})->GetInt());
+      EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    }
+    {
+      const auto root =
+          base::JSONReader().Read(post_interceptor_->GetRequestBody(1));
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(3383, app.FindPath({"ping", "ad"})->GetInt());
+      EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+      EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+    }
+    {
+      const auto root =
+          base::JSONReader().Read(post_interceptor_->GetRequestBody(2));
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+      EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+    }
+  } else {
+    EXPECT_THAT(post_interceptor_->GetRequestBody(0),
+                testing::HasSubstr(R"(<ping a="10" r="-2"/>)"));
+    EXPECT_THAT(
+        post_interceptor_->GetRequestBody(1),
+        testing::HasSubstr(R"(<ping ad="3383" rd="3383" ping_freshness=)"));
+    EXPECT_THAT(post_interceptor_->GetRequestBody(2),
+                testing::HasSubstr(R"(<ping rd="3383" ping_freshness=)"));
+  }
 }
 
-TEST_F(UpdateCheckerTest, UpdateCheckInstallSource) {
+TEST_P(UpdateCheckerTest, UpdateCheckInstallSource) {
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
   IdToComponentPtrMap components;
@@ -624,68 +829,113 @@
   auto& component = components[kUpdateItemId];
   auto crx_component = component->crx_component();
 
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
+  if (is_foreground_) {
+    {
+      auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+          config_->test_url_loader_factory());
+      EXPECT_TRUE(post_interceptor->ExpectRequest(
+          std::make_unique<PartialMatch>("updatecheck"),
+          test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                              : "updatecheck_reply_1.xml")));
+      update_checker_->CheckForUpdates(
+          update_context_->session_id, {kUpdateItemId}, components, {}, false,
+          base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                         base::Unretained(this)));
+      RunThreads();
+      const auto& request = post_interceptor->GetRequestBody(0);
+      if (use_JSON_) {
+        const auto root = base::JSONReader().Read(request);
+        const auto& app =
+            root->FindKey("request")->FindKey("app")->GetList()[0];
+        EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+        EXPECT_FALSE(app.FindKey("installedby"));
+      } else {
+        EXPECT_THAT(request, testing::HasSubstr(R"(installsource="ondemand")"));
+        EXPECT_THAT(request,
+                    testing::Not(testing::HasSubstr(R"(installedby=)")));
+      }
+    }
+    {
+      auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+          config_->test_url_loader_factory());
+      EXPECT_TRUE(post_interceptor->ExpectRequest(
+          std::make_unique<PartialMatch>("updatecheck"),
+          test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                              : "updatecheck_reply_1.xml")));
+      crx_component->install_source = "sideload";
+      crx_component->install_location = "policy";
+      component->set_crx_component(*crx_component);
+      update_checker_->CheckForUpdates(
+          update_context_->session_id, {kUpdateItemId}, components, {}, false,
+          base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                         base::Unretained(this)));
+      RunThreads();
+      const auto& request = post_interceptor->GetRequestBody(0);
+      if (use_JSON_) {
+        const auto root = base::JSONReader().Read(request);
+        const auto& app =
+            root->FindKey("request")->FindKey("app")->GetList()[0];
+        EXPECT_EQ("sideload", app.FindKey("installsource")->GetString());
+        EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+      } else {
+        EXPECT_THAT(request, testing::HasSubstr(R"(installsource="sideload")"));
+        EXPECT_THAT(request, testing::HasSubstr(R"(installedby="policy")"));
+      }
+    }
+    return;
+  }
 
-  EXPECT_THAT(post_interceptor_->GetRequestBody(0),
-              testing::Not(testing::HasSubstr("installsource=")));
-
-  update_context_->is_foreground = true;
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body1 = post_interceptor_->GetRequestBody(1);
-  EXPECT_THAT(body1, testing::HasSubstr(R"(installsource="ondemand")"));
-  EXPECT_THAT(body1, testing::Not(testing::HasSubstr(R"(installedby=)")));
-
-  update_context_->is_foreground = false;
-  crx_component->install_source = "webstore";
-  crx_component->install_location = "external";
-  component->set_crx_component(*crx_component);
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body2 = post_interceptor_->GetRequestBody(2);
-  EXPECT_THAT(body2, testing::HasSubstr(R"(installsource="webstore")"));
-  EXPECT_THAT(body2, testing::HasSubstr(R"(installedby="external")"));
-
-  update_context_->is_foreground = true;
-  crx_component->install_source = "sideload";
-  crx_component->install_location = "policy";
-  component->set_crx_component(*crx_component);
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body3 = post_interceptor_->GetRequestBody(3);
-  EXPECT_THAT(body3, testing::HasSubstr(R"(installsource="sideload")"));
-  EXPECT_THAT(body3, testing::HasSubstr(R"(installedby="policy")"));
+  DCHECK(!is_foreground_);
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_FALSE(app.FindKey("installsource"));
+    } else {
+      EXPECT_THAT(request, testing::Not(testing::HasSubstr("installsource=")));
+    }
+  }
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    crx_component->install_source = "webstore";
+    crx_component->install_location = "external";
+    component->set_crx_component(*crx_component);
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ("webstore", app.FindKey("installsource")->GetString());
+      EXPECT_EQ("external", app.FindKey("installedby")->GetString());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(installsource="webstore")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(installedby="external")"));
+    }
+  }
 }
 
-TEST_F(UpdateCheckerTest, ComponentDisabled) {
+TEST_P(UpdateCheckerTest, ComponentDisabled) {
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
   IdToComponentPtrMap components;
@@ -694,105 +944,179 @@
   auto& component = components[kUpdateItemId];
   auto crx_component = component->crx_component();
 
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
+  {
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_FALSE(app.FindKey("disabled"));
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="1")"));
+      EXPECT_THAT(request, testing::Not(testing::HasSubstr("<disabled")));
+    }
+  }
 
-  const auto& body0 = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(body0, testing::HasSubstr(R"(enabled="1")"));
-  EXPECT_THAT(body0, testing::Not(testing::HasSubstr("<disabled")));
+  {
+    crx_component->disabled_reasons = {};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_FALSE(app.FindKey("disabled"));
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="1")"));
+      EXPECT_THAT(request, testing::Not(testing::HasSubstr("<disabled")));
+    }
+  }
 
-  crx_component->disabled_reasons = {};
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
+  {
+    crx_component->disabled_reasons = {0};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+      const auto& disabled = app.FindKey("disabled")->GetList();
+      EXPECT_EQ(1u, disabled.size());
+      EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="0")"));
+    }
+  }
+  {
+    crx_component->disabled_reasons = {1};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+      const auto& disabled = app.FindKey("disabled")->GetList();
+      EXPECT_EQ(1u, disabled.size());
+      EXPECT_EQ(1, disabled[0].FindKey("reason")->GetInt());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="1")"));
+    }
+  }
 
-  const auto& body1 = post_interceptor_->GetRequestBody(1);
-  EXPECT_THAT(body1, testing::HasSubstr(R"(enabled="1")"));
-  EXPECT_THAT(body1, testing::Not(testing::HasSubstr("<disabled")));
+  {
+    crx_component->disabled_reasons = {4, 8, 16};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+      const auto& disabled = app.FindKey("disabled")->GetList();
+      EXPECT_EQ(3u, disabled.size());
+      EXPECT_EQ(4, disabled[0].FindKey("reason")->GetInt());
+      EXPECT_EQ(8, disabled[1].FindKey("reason")->GetInt());
+      EXPECT_EQ(16, disabled[2].FindKey("reason")->GetInt());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="4")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="8")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="16")"));
+    }
+  }
 
-  crx_component->disabled_reasons = {0};
-  component->set_crx_component(*crx_component);
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body2 = post_interceptor_->GetRequestBody(2);
-  EXPECT_THAT(body2, testing::HasSubstr(R"(enabled="0")"));
-  EXPECT_THAT(body2, testing::HasSubstr(R"(<disabled reason="0")"));
-
-  crx_component->disabled_reasons = {1};
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body3 = post_interceptor_->GetRequestBody(3);
-  EXPECT_THAT(body3, testing::HasSubstr(R"(enabled="0")"));
-  EXPECT_THAT(body3, testing::HasSubstr(R"(<disabled reason="1")"));
-
-  crx_component->disabled_reasons = {4, 8, 16};
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body4 = post_interceptor_->GetRequestBody(4);
-  EXPECT_THAT(body4, testing::HasSubstr(R"(enabled="0")"));
-  EXPECT_THAT(body4, testing::HasSubstr(R"(<disabled reason="4")"));
-  EXPECT_THAT(body4, testing::HasSubstr(R"(<disabled reason="8")"));
-  EXPECT_THAT(body4, testing::HasSubstr(R"(<disabled reason="16")"));
-
-  crx_component->disabled_reasons = {0, 4, 8, 16};
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-
-  const auto& body5 = post_interceptor_->GetRequestBody(5);
-  EXPECT_THAT(body5, testing::HasSubstr(R"(enabled="0")"));
-  EXPECT_THAT(body5, testing::HasSubstr(R"(<disabled reason="0")"));
-  EXPECT_THAT(body5, testing::HasSubstr(R"(<disabled reason="4")"));
-  EXPECT_THAT(body5, testing::HasSubstr(R"(<disabled reason="8")"));
-  EXPECT_THAT(body5, testing::HasSubstr(R"(<disabled reason="16")"));
+  {
+    crx_component->disabled_reasons = {0, 4, 8, 16};
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+      const auto& disabled = app.FindKey("disabled")->GetList();
+      EXPECT_EQ(4u, disabled.size());
+      EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+      EXPECT_EQ(4, disabled[1].FindKey("reason")->GetInt());
+      EXPECT_EQ(8, disabled[2].FindKey("reason")->GetInt());
+      EXPECT_EQ(16, disabled[3].FindKey("reason")->GetInt());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(R"(enabled="0")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="0")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="4")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="8")"));
+      EXPECT_THAT(request, testing::HasSubstr(R"(<disabled reason="16")"));
+    }
+  }
 }
 
-TEST_F(UpdateCheckerTest, UpdateCheckUpdateDisabled) {
+TEST_P(UpdateCheckerTest, UpdateCheckUpdateDisabled) {
   config_->SetBrand("");
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -802,93 +1126,147 @@
   auto& component = components[kUpdateItemId];
   auto crx_component = component->crx_component();
 
-  // Tests the scenario where:
-  //  * the component does not support group policies.
-  //  * the component updates are disabled.
-  // Expects the group policy to be ignored and the update check to not
-  // include the "updatedisabled" attribute.
-  EXPECT_FALSE(crx_component->supports_group_policy_enable_component_updates);
-
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-  EXPECT_THAT(
-      post_interceptor_->GetRequestBody(0),
-      testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                         R"(" version="0.9" enabled="1"><updatecheck/>)"));
-
-  // Tests the scenario where:
-  //  * the component supports group policies.
-  //  * the component updates are disabled.
-  // Expects the update check to include the "updatedisabled" attribute.
-  crx_component->supports_group_policy_enable_component_updates = true;
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, false,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-  EXPECT_THAT(
-      post_interceptor_->GetRequestBody(1),
-      testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                         R"(" version="0.9" enabled="1">)" +
-                         R"(<updatecheck updatedisabled="true"/>)"));
-
-  // Tests the scenario where:
-  //  * the component does not support group policies.
-  //  * the component updates are enabled.
-  // Expects the update check to not include the "updatedisabled" attribute.
-  crx_component->supports_group_policy_enable_component_updates = false;
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, true,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-  EXPECT_THAT(
-      post_interceptor_->GetRequestBody(2),
-      testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                         R"(" version="0.9" enabled="1"><updatecheck/>)"));
-
-  // Tests the scenario where:
-  //  * the component supports group policies.
-  //  * the component updates are enabled.
-  // Expects the update check to not include the "updatedisabled" attribute.
-  crx_component->supports_group_policy_enable_component_updates = true;
-  component->set_crx_component(*crx_component);
-  update_checker_ = UpdateChecker::Create(config_, metadata_.get());
-  EXPECT_TRUE(post_interceptor_->ExpectRequest(
-      std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
-  update_checker_->CheckForUpdates(
-      update_context_->session_id, {kUpdateItemId}, components, {}, true,
-      base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
-                     base::Unretained(this)));
-  RunThreads();
-  EXPECT_THAT(
-      post_interceptor_->GetRequestBody(3),
-      testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
-                         R"(" version="0.9" enabled="1"><updatecheck/>)"));
+  // Ignore this test parameter to keep the test simple.
+  update_context_->is_foreground = false;
+  {
+    // Tests the scenario where:
+    //  * the component does not support group policies.
+    //  * the component updates are disabled.
+    // Expects the group policy to be ignored and the update check to not
+    // include the "updatedisabled" attribute.
+    EXPECT_FALSE(crx_component->supports_group_policy_enable_component_updates);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+      EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+    } else {
+      EXPECT_THAT(
+          request,
+          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
+                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
+    }
+  }
+  {
+    // Tests the scenario where:
+    //  * the component supports group policies.
+    //  * the component updates are disabled.
+    // Expects the update check to include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = true;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, false,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+      EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_TRUE(app.FindPath({"updatecheck", "updatedisabled"})->GetBool());
+    } else {
+      EXPECT_THAT(request, testing::HasSubstr(
+                               std::string(R"(<app appid=")") + kUpdateItemId +
+                               R"(" version="0.9" enabled="1">)" +
+                               R"(<updatecheck updatedisabled="true"/>)"));
+    }
+  }
+  {
+    // Tests the scenario where:
+    //  * the component does not support group policies.
+    //  * the component updates are enabled.
+    // Expects the update check to not include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = false;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, true,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+      EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+    } else {
+      EXPECT_THAT(
+          request,
+          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
+                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
+    }
+  }
+  {
+    // Tests the scenario where:
+    //  * the component supports group policies.
+    //  * the component updates are enabled.
+    // Expects the update check to not include the "updatedisabled" attribute.
+    crx_component->supports_group_policy_enable_component_updates = true;
+    component->set_crx_component(*crx_component);
+    auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+        config_->test_url_loader_factory());
+    EXPECT_TRUE(post_interceptor->ExpectRequest(
+        std::make_unique<PartialMatch>("updatecheck"),
+        test_file(use_JSON_ ? "updatecheck_reply_1.json"
+                            : "updatecheck_reply_1.xml")));
+    update_checker_->CheckForUpdates(
+        update_context_->session_id, {kUpdateItemId}, components, {}, true,
+        base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+                       base::Unretained(this)));
+    RunThreads();
+    const auto& request = post_interceptor->GetRequestBody(0);
+    if (use_JSON_) {
+      const auto root = base::JSONReader().Read(request);
+      const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+      EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+      EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+      EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+      EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+    } else {
+      EXPECT_THAT(
+          request,
+          testing::HasSubstr(std::string(R"(<app appid=")") + kUpdateItemId +
+                             R"(" version="0.9" enabled="1"><updatecheck/>)"));
+    }
+  }
 }
 
-TEST_F(UpdateCheckerTest, NoUpdateActionRun) {
+TEST_P(UpdateCheckerTest, NoUpdateActionRun) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_noupdate.xml")));
-
+      test_file(use_JSON_ ? "updatecheck_reply_noupdate.json"
+                          : "updatecheck_reply_noupdate.xml")));
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
   IdToComponentPtrMap components;
@@ -912,13 +1290,15 @@
   EXPECT_EQ(1u, results_->list.size());
   const auto& result = results_->list.front();
   EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", result.extension_id.c_str());
+  EXPECT_STREQ("noupdate", result.status.c_str());
   EXPECT_STREQ("this", result.action_run.c_str());
 }
 
-TEST_F(UpdateCheckerTest, UpdatePauseResume) {
+TEST_P(UpdateCheckerTest, UpdatePauseResume) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_1.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_noupdate.json"
+                          : "updatecheck_reply_noupdate.xml")));
   post_interceptor_->url_job_request_ready_callback(base::BindOnce(
       [](URLLoaderPostInterceptor* post_interceptor) {
         post_interceptor->Resume();
@@ -931,6 +1311,9 @@
   IdToComponentPtrMap components;
   components[kUpdateItemId] = MakeComponent();
 
+  // Ignore this test parameter to keep the test simple.
+  update_context_->is_foreground = false;
+
   update_checker_->CheckForUpdates(
       update_context_->session_id, {kUpdateItemId}, components, {}, true,
       base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
@@ -938,18 +1321,34 @@
   RunThreads();
 
   const auto& request = post_interceptor_->GetRequestBody(0);
-  EXPECT_THAT(request, testing::HasSubstr(
-                           std::string(R"(<app appid=")") + kUpdateItemId +
-                           R"(" version="0.9" brand="TEST" enabled="1">)" +
-                           R"(<updatecheck/><ping r="-2"/>)"));
-  EXPECT_THAT(
-      request,
-      testing::HasSubstr(R"(<packages><package fp="fp1"/></packages></app>)"));
+  if (use_JSON_) {
+    const auto root = base::JSONReader().Read(request);
+    const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+    EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+    EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+    EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+    EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+    EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+    EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+    EXPECT_EQ("fp1", app.FindKey("packages")
+                         ->FindKey("package")
+                         ->GetList()[0]
+                         .FindKey("fp")
+                         ->GetString());
+  } else {
+    EXPECT_THAT(request, testing::HasSubstr(
+                             std::string(R"(<app appid=")") + kUpdateItemId +
+                             R"(" version="0.9" brand="TEST" enabled="1">)" +
+                             R"(<updatecheck/><ping r="-2"/>)"));
+    EXPECT_THAT(request,
+                testing::HasSubstr(
+                    R"(<packages><package fp="fp1"/></packages></app>)"));
+  }
 }
 
 // Tests that an update checker object and its underlying SimpleURLLoader can
 // be safely destroyed while it is paused.
-TEST_F(UpdateCheckerTest, UpdateResetUpdateChecker) {
+TEST_P(UpdateCheckerTest, UpdateResetUpdateChecker) {
   base::RunLoop runloop;
   auto quit_closure = runloop.QuitClosure();
 
@@ -974,10 +1373,11 @@
 
 // The update response contains a protocol version which does not match the
 // expected protocol version.
-TEST_F(UpdateCheckerTest, ParseErrorProtocolVersionMismatch) {
+TEST_P(UpdateCheckerTest, ParseErrorProtocolVersionMismatch) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_parse_error.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_parse_error.json"
+                          : "updatecheck_reply_parse_error.xml")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
@@ -1003,10 +1403,11 @@
 // The update response contains a status |error-unknownApplication| for the
 // app. The response is succesfully parsed and a result is extracted to
 // indicate this status.
-TEST_F(UpdateCheckerTest, ParseErrorAppStatusErrorUnknownApplication) {
+TEST_P(UpdateCheckerTest, ParseErrorAppStatusErrorUnknownApplication) {
   EXPECT_TRUE(post_interceptor_->ExpectRequest(
       std::make_unique<PartialMatch>("updatecheck"),
-      test_file("updatecheck_reply_unknownapp.xml")));
+      test_file(use_JSON_ ? "updatecheck_reply_unknownapp.json"
+                          : "updatecheck_reply_unknownapp.xml")));
 
   update_checker_ = UpdateChecker::Create(config_, metadata_.get());
 
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc
index 43ec49e..2f67919e 100644
--- a/components/update_client/update_client_unittest.cc
+++ b/components/update_client/update_client_unittest.cc
@@ -3710,8 +3710,8 @@
       /*
       "<event eventtype="14" eventresult="1" downloader="unknown" "
       "url="http://localhost/download/runaction_test_win.crx3"
-      "downloaded="1843" "
-      "total="1843" download_time_ms="1000" previousversion="0.0" "
+      "downloaded=1843 "
+      "total=1843 download_time_ms="1000" previousversion="0.0" "
       "nextversion="1.0"/>"
       */
       const auto& event0 = events()[0];
@@ -3720,9 +3720,9 @@
       EXPECT_EQ("unknown", event0.FindKey("downloader")->GetString());
       EXPECT_EQ("http://localhost/download/runaction_test_win.crx3",
                 event0.FindKey("url")->GetString());
-      EXPECT_EQ("1843", event0.FindKey("downloaded")->GetString());
-      EXPECT_EQ("1843", event0.FindKey("total")->GetString());
-      EXPECT_EQ("1000", event0.FindKey("download_time_ms")->GetString());
+      EXPECT_EQ(1843, event0.FindKey("downloaded")->GetDouble());
+      EXPECT_EQ(1843, event0.FindKey("total")->GetDouble());
+      EXPECT_EQ(1000, event0.FindKey("download_time_ms")->GetDouble());
       EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
       EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
 
diff --git a/content/browser/android/web_contents_observer_proxy.cc b/content/browser/android/web_contents_observer_proxy.cc
index 84b5241..5466fb5 100644
--- a/content/browser/android/web_contents_observer_proxy.cc
+++ b/content/browser/android/web_contents_observer_proxy.cc
@@ -11,6 +11,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/android/media_metadata_android.h"
@@ -162,6 +163,8 @@
   ScopedJavaLocalRef<jstring> jerror_description =
       ConvertUTF8ToJavaString(env, "");
 
+  // Remove after fixing https://crbug/905461.
+  TRACE_EVENT0("browser", "Java_WebContentsObserverProxy_didFinishNavigation");
   Java_WebContentsObserverProxy_didFinishNavigation(
       env, java_observer_, jstring_url, navigation_handle->IsInMainFrame(),
       navigation_handle->IsErrorPage(), navigation_handle->HasCommitted(),
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index a44d629..fd46871 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3099,13 +3099,19 @@
         }
       }
 
-      // Call testing callback functions for each event to fire.
-      for (size_t i = 0; i < details.events.size(); i++) {
-        if (static_cast<int>(details.events[i].event_type) < 0)
-          continue;
+      if (details.events.empty()) {
+        // Objects were marked dirty but no events were provided.
+        // The callback must still run, otherwise dump event tests can hang.
+        accessibility_testing_callback_.Run(this, ax::mojom::Event::kNone, 0);
+      } else {
+        // Call testing callback functions for each event to fire.
+        for (size_t i = 0; i < details.events.size(); i++) {
+          if (static_cast<int>(details.events[i].event_type) < 0)
+            continue;
 
-        accessibility_testing_callback_.Run(this, details.events[i].event_type,
-                                            details.events[i].id);
+          accessibility_testing_callback_.Run(
+              this, details.events[i].event_type, details.events[i].id);
+        }
       }
     }
   }
diff --git a/content/browser/media/media_internals_audio_focus_helper.h b/content/browser/media/media_internals_audio_focus_helper.h
index 8e3d344..ee977a8 100644
--- a/content/browser/media/media_internals_audio_focus_helper.h
+++ b/content/browser/media/media_internals_audio_focus_helper.h
@@ -29,6 +29,8 @@
                      media_session::mojom::AudioFocusType type) override;
   void OnFocusLost(
       media_session::mojom::MediaSessionInfoPtr media_session) override;
+  void OnActiveSessionChanged(
+      media_session::mojom::AudioFocusRequestStatePtr session) override {}
 
   // Sets whether we should listen to audio focus events.
   void SetEnabled(bool enabled);
diff --git a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc
index ca3fcf25..7fe6479e 100644
--- a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc
+++ b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <memory>
+#include <utility>
 
 #include "base/logging.h"
 #include "base/task/post_task.h"
@@ -19,6 +20,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/socket_permission_request.h"
 #include "net/base/address_list.h"
+#include "net/dns/public/dns_query_type.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/c/private/ppb_host_resolver_private.h"
 #include "ppapi/c/private/ppb_net_address_private.h"
@@ -27,6 +29,7 @@
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/proxy/ppapi_messages.h"
 #include "ppapi/shared_impl/private/net_address_private_impl.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 
 using ppapi::host::NetErrorToPepperError;
 using ppapi::host::ReplyMessageContext;
@@ -39,13 +42,13 @@
                         network::mojom::ResolveHostParameters* params) {
   switch (hint.family) {
     case PP_NETADDRESSFAMILY_PRIVATE_IPV4:
-      params->dns_query_type = net::HostResolver::DnsQueryType::A;
+      params->dns_query_type = net::DnsQueryType::A;
       break;
     case PP_NETADDRESSFAMILY_PRIVATE_IPV6:
-      params->dns_query_type = net::HostResolver::DnsQueryType::AAAA;
+      params->dns_query_type = net::DnsQueryType::AAAA;
       break;
     default:
-      params->dns_query_type = net::HostResolver::DnsQueryType::UNSPECIFIED;
+      params->dns_query_type = net::DnsQueryType::UNSPECIFIED;
   }
 
   if (hint.flags & PP_HOST_RESOLVER_PRIVATE_FLAGS_CANONNAME)
diff --git a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
index 80b1e5c..8840d0d 100644
--- a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
+++ b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
@@ -17,7 +17,7 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "ppapi/c/pp_instance.h"
 #include "ppapi/host/resource_message_filter.h"
-#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 
 struct PP_HostResolver_Private_Hint;
 struct PP_NetAddress_Private;
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 3166cbe2..658cdc9 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -137,23 +137,19 @@
   // Returns the information to display when a navigation error occurs.
   // If |error_html| is not null then it may be set to a HTML page
   // containing the details of the error and maybe links to more info.
-  // If |error_description| is not null it may be set to contain a brief
-  // message describing the error that has occurred.
-  // Either of the out parameters may be not written to in certain cases
+  // Note that |error_html| may be not written to in certain cases
   // (lack of information on the error code) so the caller should take care to
-  // initialize the string values with safe defaults before the call.
+  // initialize it with a safe default before the call.
   virtual void PrepareErrorPage(content::RenderFrame* render_frame,
                                 const blink::WebURLRequest& failed_request,
                                 const blink::WebURLError& error,
-                                std::string* error_html,
-                                base::string16* error_description) {}
+                                std::string* error_html) {}
   virtual void PrepareErrorPageForHttpStatusError(
       content::RenderFrame* render_frame,
       const blink::WebURLRequest& failed_request,
       const GURL& unreachable_url,
       int http_status,
-      std::string* error_html,
-      base::string16* error_description) {}
+      std::string* error_html) {}
 
   // Returns as |error_description| a brief description of the error that
   // ocurred. The out parameter may be not written to in certain cases (lack of
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h
index 9120bc7..15f9c394 100644
--- a/content/public/test/test_renderer_host.h
+++ b/content/public/test/test_renderer_host.h
@@ -260,6 +260,8 @@
   // context.
   virtual BrowserContext* GetBrowserContext();
 
+  TestBrowserThreadBundle* thread_bundle() { return thread_bundle_.get(); }
+
 #if defined(USE_AURA)
   aura::Window* root_window() { return aura_test_helper_->root_window(); }
 #endif
diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc
index de1114a..760115ba 100644
--- a/content/renderer/accessibility/render_accessibility_impl.cc
+++ b/content/renderer/accessibility/render_accessibility_impl.cc
@@ -415,7 +415,7 @@
   if (document.IsNull())
     return;
 
-  if (pending_events_.empty())
+  if (pending_events_.empty() && dirty_objects_.empty())
     return;
 
   ack_pending_ = true;
diff --git a/content/renderer/browser_render_view_browsertest.cc b/content/renderer/browser_render_view_browsertest.cc
index 6d6bd0d..2fe5e36 100644
--- a/content/renderer/browser_render_view_browsertest.cc
+++ b/content/renderer/browser_render_view_browsertest.cc
@@ -57,8 +57,7 @@
   void PrepareErrorPage(content::RenderFrame* render_frame,
                         const blink::WebURLRequest& failed_request,
                         const blink::WebURLError& error,
-                        std::string* error_html,
-                        base::string16* error_description) override {
+                        std::string* error_html) override {
     if (error_html)
       *error_html = "A suffusion of yellow.";
     latest_error_valid_ = true;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index d7d8462..ed3a350c 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2826,33 +2826,19 @@
   std::string error_html;
   if (error_page_content.has_value()) {
     error_html = error_page_content.value();
+    // We don't need the actual error page content, but still call this
+    // for any possible side effects.
     GetContentClient()->renderer()->PrepareErrorPage(this, failed_request,
-                                                     error, nullptr, nullptr);
+                                                     error, nullptr);
   } else {
-    GetContentClient()->renderer()->PrepareErrorPage(
-        this, failed_request, error, &error_html, nullptr);
+    GetContentClient()->renderer()->PrepareErrorPage(this, failed_request,
+                                                     error, &error_html);
   }
   LoadNavigationErrorPageInternal(error_html, error.url(), replace, entry,
                                   std::move(navigation_params),
                                   std::move(navigation_data), &failed_request);
 }
 
-void RenderFrameImpl::LoadNavigationErrorPageForHttpStatusError(
-    const WebURLRequest& failed_request,
-    const GURL& unreachable_url,
-    int http_status,
-    bool replace,
-    HistoryEntry* entry,
-    std::unique_ptr<blink::WebNavigationParams> navigation_params,
-    std::unique_ptr<blink::WebDocumentLoader::ExtraData> navigation_data) {
-  std::string error_html;
-  GetContentClient()->renderer()->PrepareErrorPageForHttpStatusError(
-      this, failed_request, unreachable_url, http_status, &error_html, nullptr);
-  LoadNavigationErrorPageInternal(error_html, unreachable_url, replace, entry,
-                                  std::move(navigation_params),
-                                  std::move(navigation_data), &failed_request);
-}
-
 void RenderFrameImpl::LoadNavigationErrorPageInternal(
     const std::string& error_html,
     const GURL& error_url,
@@ -3005,8 +2991,7 @@
 
   std::string error_html;
   GetContentClient()->renderer()->PrepareErrorPage(
-      this, frame_->GetDocumentLoader()->GetRequest(), error, &error_html,
-      nullptr);
+      this, frame_->GetDocumentLoader()->GetRequest(), error, &error_html);
 
   LoadNavigationErrorPageInternal(
       error_html, error.url(), true /* replace */, nullptr /* history_entry */,
@@ -4574,10 +4559,15 @@
     navigation_params->service_worker_network_provider =
         BuildServiceWorkerNetworkProviderForNavigation(
             nullptr /* request_params */, nullptr /* controller_info */);
-    LoadNavigationErrorPageForHttpStatusError(
-        frame_->GetDocumentLoader()->GetRequest(), frame_->GetDocument().Url(),
-        http_status_code, true /* replace */, nullptr /* entry */,
-        std::move(navigation_params), std::move(document_state));
+    WebURLRequest failed_request = frame_->GetDocumentLoader()->GetRequest();
+    WebURL unreachable_url = frame_->GetDocument().Url();
+    std::string error_html;
+    GetContentClient()->renderer()->PrepareErrorPageForHttpStatusError(
+        this, failed_request, unreachable_url, http_status_code, &error_html);
+    LoadNavigationErrorPageInternal(error_html, unreachable_url,
+                                    true /* replace */, nullptr /* entry */,
+                                    std::move(navigation_params),
+                                    std::move(document_state), &failed_request);
   }
   // Do not use |this| or |frame_| here without checking |weak_self|.
 }
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index bfdf0879..1795ed3 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1142,14 +1142,6 @@
       const base::Optional<std::string>& error_page_content,
       std::unique_ptr<blink::WebNavigationParams> navigation_params,
       std::unique_ptr<blink::WebDocumentLoader::ExtraData> navigation_data);
-  void LoadNavigationErrorPageForHttpStatusError(
-      const blink::WebURLRequest& failed_request,
-      const GURL& unreachable_url,
-      int http_status,
-      bool replace,
-      HistoryEntry* entry,
-      std::unique_ptr<blink::WebNavigationParams> navigation_params,
-      std::unique_ptr<blink::WebDocumentLoader::ExtraData> navigation_data);
   void LoadNavigationErrorPageInternal(
       const std::string& error_html,
       const GURL& error_url,
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 3ed189a8..c3bc1e6 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -1983,8 +1983,7 @@
     void PrepareErrorPage(content::RenderFrame* render_frame,
                           const blink::WebURLRequest& failed_request,
                           const blink::WebURLError& error,
-                          std::string* error_html,
-                          base::string16* error_description) override {
+                          std::string* error_html) override {
       if (error_html)
         *error_html = "A suffusion of yellow.";
     }
@@ -1993,8 +1992,7 @@
         const blink::WebURLRequest& failed_request,
         const GURL& url,
         int http_status_code,
-        std::string* error_html,
-        base::string16* error_description) override {
+        std::string* error_html) override {
       if (error_html)
         *error_html = "A suffusion of yellow.";
     }
diff --git a/content/shell/renderer/shell_content_renderer_client.cc b/content/shell/renderer/shell_content_renderer_client.cc
index 7ea4173..5a50a0ec 100644
--- a/content/shell/renderer/shell_content_renderer_client.cc
+++ b/content/shell/renderer/shell_content_renderer_client.cc
@@ -137,8 +137,7 @@
     RenderFrame* render_frame,
     const blink::WebURLRequest& failed_request,
     const blink::WebURLError& error,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   if (error_html && error_html->empty()) {
     *error_html =
         "<head><title>Error</title></head><body>Could not load the requested "
@@ -155,8 +154,7 @@
     const blink::WebURLRequest& failed_request,
     const GURL& unreachable_url,
     int http_status,
-    std::string* error_html,
-    base::string16* error_description) {
+    std::string* error_html) {
   if (error_html) {
     *error_html =
         "<head><title>Error</title></head><body>Server returned HTTP status " +
diff --git a/content/shell/renderer/shell_content_renderer_client.h b/content/shell/renderer/shell_content_renderer_client.h
index 2dd4662..e3a9864 100644
--- a/content/shell/renderer/shell_content_renderer_client.h
+++ b/content/shell/renderer/shell_content_renderer_client.h
@@ -31,15 +31,13 @@
   void PrepareErrorPage(RenderFrame* render_frame,
                         const blink::WebURLRequest& failed_request,
                         const blink::WebURLError& error,
-                        std::string* error_html,
-                        base::string16* error_description) override;
+                        std::string* error_html) override;
   void PrepareErrorPageForHttpStatusError(
       content::RenderFrame* render_frame,
       const blink::WebURLRequest& failed_request,
       const GURL& unreachable_url,
       int http_status,
-      std::string* error_html,
-      base::string16* error_description) override;
+      std::string* error_html) override;
 
   // TODO(mkwst): These toggle based on the kEnablePepperTesting flag. Do we
   // need that outside of layout tests?
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 12afb18..3c2457e 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -310,9 +310,6 @@
     self.Fail('conformance/rendering/rendering-stencil-large-viewport.html',
         ['win', 'intel', 'd3d11'], bug=782317)
 
-    # Seems to cause the harness to fail immediately afterward
-    self.Skip('conformance2/textures/video/tex-2d-rgba16f-rgba-half_float.html',
-        ['win', 'intel', 'd3d11'], bug=648337)
     # TODO(kbr): re-enable after fix for http://crbug.com/898350
     # self.Flaky('deqp/functional/gles3/lifetime.html',
     #     ['win', 'intel', 'd3d11'], bug=620379)
diff --git a/device/fido/fido_discovery_factory.cc b/device/fido/fido_discovery_factory.cc
index 5386d6af0..bcf26c5 100644
--- a/device/fido/fido_discovery_factory.cc
+++ b/device/fido/fido_discovery_factory.cc
@@ -33,19 +33,6 @@
   return nullptr;
 #else
 
-#if defined(OS_WIN)
-  // On platforms where the Windows webauthn.dll is present, access to USB
-  // devices is blocked and we use a special authenticator that forwards
-  // requests to the Windows WebAuthn API instead.
-  if (base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi) &&
-      WinWebAuthnApi::GetDefault()->IsAvailable()) {
-    return std::make_unique<WinNativeCrossPlatformAuthenticatorDiscovery>(
-        WinWebAuthnApi::GetDefault(),
-        // TODO(martinkr): Inject the window from which the request originated.
-        GetForegroundWindow());
-  }
-#endif  // defined(OS_WIN)
-
   DCHECK(connector);
   return std::make_unique<FidoHidDiscovery>(connector);
 #endif  // !defined(OS_ANDROID)
@@ -74,13 +61,13 @@
   return nullptr;
 }
 
-}  // namespace
-
 std::unique_ptr<FidoDiscoveryBase> CreateCableDiscoveryImpl(
     std::vector<CableDiscoveryData> cable_data) {
   return std::make_unique<FidoCableDiscovery>(std::move(cable_data));
 }
 
+}  // namespace
+
 // static
 FidoDiscoveryFactory::FactoryFuncPtr FidoDiscoveryFactory::g_factory_func_ =
     &CreateFidoDiscoveryImpl;
@@ -102,6 +89,26 @@
   return (*g_cable_factory_func_)(std::move(cable_data));
 }
 
+//  static
+#if defined(OS_WIN)
+std::unique_ptr<FidoDiscoveryBase>
+FidoDiscoveryFactory::MaybeCreateWinWebAuthnApiDiscovery() {
+  if (!base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi) ||
+      !WinWebAuthnApi::GetDefault()->IsAvailable()) {
+    return nullptr;
+  }
+  return std::make_unique<WinWebAuthnApiAuthenticatorDiscovery>(
+      WinWebAuthnApi::GetDefault(),
+      // TODO(martinkr): Inject the window from which the request
+      // originated. Windows uses this parameter to center the
+      // dialog over the parent. The dialog should be centered
+      // over the originating Chrome Window; the foreground window
+      // may have changed to something else since the request was
+      // issued.
+      GetForegroundWindow());
+}
+#endif  // defined(OS_WIN)
+
 // ScopedFidoDiscoveryFactory -------------------------------------------------
 
 namespace internal {
diff --git a/device/fido/fido_discovery_factory.h b/device/fido/fido_discovery_factory.h
index 573d8b1c..cc21790 100644
--- a/device/fido/fido_discovery_factory.h
+++ b/device/fido/fido_discovery_factory.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/component_export.h"
+#include "build/build_config.h"
 #include "device/fido/cable/cable_discovery_data.h"
 #include "device/fido/fido_device_discovery.h"
 #include "device/fido/fido_discovery_base.h"
@@ -39,6 +40,12 @@
   // Instantiates a FidoDiscovery for caBLE.
   static std::unique_ptr<FidoDiscoveryBase> CreateCable(
       std::vector<CableDiscoveryData> cable_data);
+#if defined(OS_WIN)
+  // Instantiates a FidoDiscovery for the native Windows WebAuthn
+  // API where available. Returns nullptr otherwise.
+  static std::unique_ptr<FidoDiscoveryBase>
+  MaybeCreateWinWebAuthnApiDiscovery();
+#endif  // defined(OS_WIN)
 
  private:
   friend class internal::ScopedFidoDiscoveryFactory;
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc
index 2143e19..d07ddc2 100644
--- a/device/fido/fido_request_handler_base.cc
+++ b/device/fido/fido_request_handler_base.cc
@@ -58,7 +58,16 @@
 FidoRequestHandlerBase::FidoRequestHandlerBase(
     service_manager::Connector* connector,
     const base::flat_set<FidoTransportProtocol>& available_transports)
-    : weak_factory_(this) {
+    : connector_(connector), weak_factory_(this) {
+#if defined(OS_WIN)
+  InitDiscoveriesWin(available_transports);
+#else
+  InitDiscoveries(available_transports);
+#endif  // !defined(OS_WIN)
+}
+
+void FidoRequestHandlerBase::InitDiscoveries(
+    const base::flat_set<FidoTransportProtocol>& available_transports) {
   // The number of times |notify_observer_callback_| needs to be invoked before
   // Observer::OnTransportAvailabilityEnumerated is dispatched. Essentially this
   // is used to wait until all the parts of |transport_availability_info_| are
@@ -85,7 +94,7 @@
       continue;
     }
 
-    auto discovery = FidoDiscoveryFactory::Create(transport, connector);
+    auto discovery = FidoDiscoveryFactory::Create(transport, connector_);
     if (discovery == nullptr) {
       // This can occur in tests when a ScopedVirtualU2fDevice is in effect and
       // HID transports are not configured.
@@ -116,6 +125,55 @@
           weak_factory_.GetWeakPtr()));
 }
 
+#if defined(OS_WIN)
+void FidoRequestHandlerBase::InitDiscoveriesWin(
+    const base::flat_set<FidoTransportProtocol>& available_transports) {
+  // Try to instantiate the discovery for proxying requests to the native
+  // Windows WebAuthn API; or fall back to using the regular device transport
+  // discoveries if the API is unavailable.
+  auto discovery = FidoDiscoveryFactory::MaybeCreateWinWebAuthnApiDiscovery();
+  if (!discovery) {
+    InitDiscoveries(available_transports);
+    return;
+  }
+
+  // The Windows WebAuthn API is available. On this platform, communicating
+  // with authenticator devices directly is blocked by the OS, so we need to go
+  // through the native API instead. No device discoveries may be instantiated.
+  //
+  // The Windows API supports USB, NFC, BLE and platform authenticators, but
+  // not caBLE.  Communicating with caBLE devices directly is subject to the
+  // same block by the OS, so this platform is without caBLE support for now.
+  //
+  // TODO(martinkr): Re-enable the caBLE discovery once caBLE has moved to a
+  // different UUID. See crbug.com/905111.
+
+  discovery->set_observer(this);
+  discoveries_.push_back(std::move(discovery));
+
+  // Tell the embedder to not render a UI and ignore all future callbacks. Also
+  // don't report any available transports; the embedder is not supposed to use
+  // this information anyway.
+  transport_availability_info_.disable_embedder_ui = true;
+  transport_availability_info_.available_transports = {};
+
+  // The number of times |notify_observer_callback_| needs to be invoked before
+  // Observer::OnTransportAvailabilityEnumerated is dispatched. Essentially this
+  // is used to wait until all the parts of |transport_availability_info_| are
+  // filled out. In the case of Windows, there are no transport discoveries to
+  // wait for, so the |notify_observer_callback_| is only invoked in:
+  //   1) SetPlatformAuthenticatorOrMarkUnavailable().
+  //   2) set_observer().
+  constexpr size_t transport_info_callback_count = 2u;
+
+  notify_observer_callback_ = base::BarrierClosure(
+      transport_info_callback_count,
+      base::BindOnce(
+          &FidoRequestHandlerBase::NotifyObserverTransportAvailability,
+          weak_factory_.GetWeakPtr()));
+}
+#endif  // defined(OS_WIN)
+
 FidoRequestHandlerBase::~FidoRequestHandlerBase() = default;
 
 void FidoRequestHandlerBase::StartAuthenticatorRequest(
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 575aebb..125984e 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -19,6 +19,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece_forward.h"
+#include "build/build_config.h"
 #include "device/fido/fido_device_authenticator.h"
 #include "device/fido/fido_discovery_base.h"
 #include "device/fido/fido_transport_protocol.h"
@@ -87,6 +88,12 @@
     bool has_recognized_mac_touch_id_credential = false;
     bool is_ble_powered = false;
     bool can_power_on_ble_adapter = false;
+
+    // If true, dispatch of the request cannot be controlled by
+    // the embedder. The embedder must not display a UI for this
+    // request and must ignore all subsequent invocations of the
+    // TransportAvailabilityObserver interface methods.
+    bool disable_embedder_ui = false;
   };
 
   class COMPONENT_EXPORT(DEVICE_FIDO) TransportAvailabilityObserver {
@@ -201,6 +208,13 @@
  private:
   friend class FidoRequestHandlerTest;
 
+  void InitDiscoveries(
+      const base::flat_set<FidoTransportProtocol>& available_transports);
+#if defined(OS_WIN)
+  void InitDiscoveriesWin(
+      const base::flat_set<FidoTransportProtocol>& available_transports);
+#endif
+
   // FidoDiscoveryBase::Observer
   void AuthenticatorAdded(FidoDiscoveryBase* discovery,
                           FidoAuthenticator* authenticator) final;
@@ -232,6 +246,7 @@
   // TODO(martinkr): Inject platform authenticators through a new
   // FidoDiscoveryBase specialization and hold ownership there.
   std::unique_ptr<FidoAuthenticator> platform_authenticator_;
+  service_manager::Connector* const connector_;
 
   base::WeakPtrFactory<FidoRequestHandlerBase> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(FidoRequestHandlerBase);
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index a33f76d..30391c3 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -31,6 +31,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_WIN)
+#include "device/fido/win/authenticator.h"
 #include "device/fido/win/fake_webauthn_api.h"
 #endif  // defined(OS_WIN)
 
@@ -753,10 +754,13 @@
     if (test.enable_feature_flag)
       scoped_feature_list.InitAndEnableFeature(kWebAuthUseNativeWinApi);
 
+    // Simulate a connected HID device.
+    ScopedFakeHidManager fake_hid_manager;
+    fake_hid_manager.AddFidoHidDevice("guid");
+
     TestGetAssertionRequestCallback cb;
-    ScopedFakeHidManager fake_hid_manager_;
     auto handler = std::make_unique<GetAssertionRequestHandler>(
-        fake_hid_manager_.service_manager_connector(),
+        fake_hid_manager.service_manager_connector(),
         base::flat_set<FidoTransportProtocol>(
             {FidoTransportProtocol::kUsbHumanInterfaceDevice}),
         CtapGetAssertionRequest(test_data::kRelyingPartyId,
@@ -765,14 +769,11 @@
         cb.callback());
     scoped_task_environment_.RunUntilIdle();
 
-    fake_hid_manager_.AddFidoHidDevice("guid");
-    scoped_task_environment_.RunUntilIdle();
-
     EXPECT_EQ(1u, handler->AuthenticatorsForTesting().size());
     // Crudely distinguish authenticator type by FidoAuthenticator::GetId.
     EXPECT_EQ(test.expect_device_type == DeviceType::kHid
                   ? "hid:guid"
-                  : "WinNativeCrossPlatformAuthenticator",
+                  : WinWebAuthnApiAuthenticator::kAuthenticatorId,
               handler->AuthenticatorsForTesting().begin()->second->GetId());
   }
 }
diff --git a/device/fido/win/authenticator.cc b/device/fido/win/authenticator.cc
index 9c4f4b8d..3dc9d3c0 100644
--- a/device/fido/win/authenticator.cc
+++ b/device/fido/win/authenticator.cc
@@ -39,7 +39,11 @@
 
 }  // namespace
 
-WinNativeCrossPlatformAuthenticator::WinNativeCrossPlatformAuthenticator(
+// static
+const char WinWebAuthnApiAuthenticator::kAuthenticatorId[] =
+    "WinWebAuthnApiAuthenticator";
+
+WinWebAuthnApiAuthenticator::WinWebAuthnApiAuthenticator(
     WinWebAuthnApi* win_api,
     HWND current_window)
     : FidoAuthenticator(),
@@ -50,18 +54,18 @@
   CoCreateGuid(&cancellation_id_);
 }
 
-WinNativeCrossPlatformAuthenticator::~WinNativeCrossPlatformAuthenticator() {
+WinWebAuthnApiAuthenticator::~WinWebAuthnApiAuthenticator() {
   // Cancel in order to dismiss any pending API request and UI dialog and shut
   // down |thread_|.
   Cancel();
 }
 
-void WinNativeCrossPlatformAuthenticator::InitializeAuthenticator(
+void WinWebAuthnApiAuthenticator::InitializeAuthenticator(
     base::OnceClosure callback) {
   std::move(callback).Run();
 }
 
-void WinNativeCrossPlatformAuthenticator::MakeCredential(
+void WinWebAuthnApiAuthenticator::MakeCredential(
     CtapMakeCredentialRequest request,
     MakeCredentialCallback callback) {
   DCHECK(!thread_);
@@ -78,22 +82,21 @@
   thread_->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &WinNativeCrossPlatformAuthenticator::MakeCredentialBlocking,
+          &WinWebAuthnApiAuthenticator::MakeCredentialBlocking,
           // Because |thread_| and its task runner are owned by this
           // authenticator instance, binding to Unretained(this) here is
           // fine. If the instance got destroyed before invocation of the
           // task, so would the task. Once the task is running, destruction
           // of the authenticator instance blocks on the thread exiting.
           base::Unretained(this), std::move(request),
-          base::BindOnce(&WinNativeCrossPlatformAuthenticator::
-                             InvokeMakeCredentialCallback,
-                         weak_factory_.GetWeakPtr(), std::move(callback)),
+          base::BindOnce(
+              &WinWebAuthnApiAuthenticator::InvokeMakeCredentialCallback,
+              weak_factory_.GetWeakPtr(), std::move(callback)),
           base::SequencedTaskRunnerHandle::Get()));
 }
 
-void WinNativeCrossPlatformAuthenticator::GetAssertion(
-    CtapGetAssertionRequest request,
-    GetAssertionCallback callback) {
+void WinWebAuthnApiAuthenticator::GetAssertion(CtapGetAssertionRequest request,
+                                               GetAssertionCallback callback) {
   DCHECK(!thread_);
   if (thread_) {
     return;
@@ -104,7 +107,7 @@
   thread_->task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &WinNativeCrossPlatformAuthenticator::GetAssertionBlocking,
+          &WinWebAuthnApiAuthenticator::GetAssertionBlocking,
           // Because |thread_| and its task runner are owned by this
           // authenticator instance, binding to Unretained(this) here is
           // fine. If the instance got destroyed before invocation of the
@@ -112,7 +115,7 @@
           // of the authenticator instance blocks on the thread exiting.
           base::Unretained(this), std::move(request),
           base::BindOnce(
-              &WinNativeCrossPlatformAuthenticator::InvokeGetAssertionCallback,
+              &WinWebAuthnApiAuthenticator::InvokeGetAssertionCallback,
               weak_factory_.GetWeakPtr(), std::move(callback)),
           base::SequencedTaskRunnerHandle::Get()));
 }
@@ -120,7 +123,7 @@
 // Invokes the blocking WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL API call. This
 // method is run on |thread_|. Note that the destructor for this class blocks
 // on |thread_| shutdown.
-void WinNativeCrossPlatformAuthenticator::MakeCredentialBlocking(
+void WinWebAuthnApiAuthenticator::MakeCredentialBlocking(
     CtapMakeCredentialRequest request,
     MakeCredentialCallback callback,
     scoped_refptr<base::SequencedTaskRunner> callback_runner) {
@@ -190,11 +193,10 @@
       kWinWebAuthnTimeoutMilliseconds,
       WEBAUTHN_CREDENTIALS{exclude_list.size(), exclude_list.data()},
       WEBAUTHN_EXTENSIONS{extensions.size(), extensions.data()},
-      // Forcibly set authenticator attachment to cross-platform in order to
-      // avoid triggering the platform authenticator option, which is
-      // generally displayed first in the Windows UI.
+      // TODO(martinkr): Plumb authenticator attachment into
+      // CtapMakeCredentialRequest and set here.
       use_u2f_only_ ? WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2
-                    : WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM,
+                    : WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY,
       request.resident_key_required(),
       ToWinUserVerificationRequirement(request.user_verification()),
       WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT, 0 /* flags */,
@@ -270,7 +272,7 @@
 // Invokes the blocking WEBAUTHN_AUTHENTICATOR_GET_ASSERTION API call. This
 // method is run on |thread_|. Note that the destructor for this class blocks
 // on |thread_| shutdown.
-void WinNativeCrossPlatformAuthenticator::GetAssertionBlocking(
+void WinWebAuthnApiAuthenticator::GetAssertionBlocking(
     CtapGetAssertionRequest request,
     GetAssertionCallback callback,
     scoped_refptr<base::SequencedTaskRunner> callback_runner) {
@@ -298,8 +300,10 @@
       kWinWebAuthnTimeoutMilliseconds,
       WEBAUTHN_CREDENTIALS{allow_list.size(), allow_list.data()},
       WEBAUTHN_EXTENSIONS{0, nullptr},
+      // TODO(martinkr): Plumb authenticator attachment into
+      // CtapMakeCredentialRequest and set here.
       use_u2f_only_ ? WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2
-                    : WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM,
+                    : WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY,
       ToWinUserVerificationRequirement(request.user_verification()),
       0,                                          // flags
       use_u2f_only_ ? rp_id16.c_str() : nullptr,  // pwszU2fAppId
@@ -333,7 +337,7 @@
       base::BindOnce(std::move(callback), status, std::move(response)));
 }
 
-void WinNativeCrossPlatformAuthenticator::Cancel() {
+void WinWebAuthnApiAuthenticator::Cancel() {
   if (!thread_ || operation_cancelled_.IsSet()) {
     return;
   }
@@ -345,31 +349,31 @@
   thread_->Stop();
 }
 
-std::string WinNativeCrossPlatformAuthenticator::GetId() const {
-  return "WinNativeCrossPlatformAuthenticator";
+std::string WinWebAuthnApiAuthenticator::GetId() const {
+  return kAuthenticatorId;
 }
 
-base::string16 WinNativeCrossPlatformAuthenticator::GetDisplayName() const {
-  return L"WinNativeCrossPlatformAuthenticator";
+base::string16 WinWebAuthnApiAuthenticator::GetDisplayName() const {
+  return base::UTF8ToUTF16(GetId());
 }
 
-bool WinNativeCrossPlatformAuthenticator::IsInPairingMode() const {
+bool WinWebAuthnApiAuthenticator::IsInPairingMode() const {
   return false;
 }
 
-bool WinNativeCrossPlatformAuthenticator::IsPaired() const {
+bool WinWebAuthnApiAuthenticator::IsPaired() const {
   return false;
 }
 
 base::Optional<FidoTransportProtocol>
-WinNativeCrossPlatformAuthenticator::AuthenticatorTransport() const {
+WinWebAuthnApiAuthenticator::AuthenticatorTransport() const {
   // The Windows API could potentially use any external or
   // platform authenticator.
   return base::nullopt;
 }
 
 const base::Optional<AuthenticatorSupportedOptions>&
-WinNativeCrossPlatformAuthenticator::Options() const {
+WinWebAuthnApiAuthenticator::Options() const {
   // The request can potentially be fulfilled by any device that Windows
   // communicates with, so returning AuthenticatorSupportedOptions really
   // doesn't make much sense.
@@ -378,12 +382,11 @@
   return no_options;
 }
 
-base::WeakPtr<FidoAuthenticator>
-WinNativeCrossPlatformAuthenticator::GetWeakPtr() {
+base::WeakPtr<FidoAuthenticator> WinWebAuthnApiAuthenticator::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
 
-void WinNativeCrossPlatformAuthenticator::InvokeMakeCredentialCallback(
+void WinWebAuthnApiAuthenticator::InvokeMakeCredentialCallback(
     MakeCredentialCallback cb,
     CtapDeviceResponseCode status,
     base::Optional<AuthenticatorMakeCredentialResponse> response) {
@@ -394,7 +397,7 @@
   }
   std::move(cb).Run(status, std::move(response));
 }
-void WinNativeCrossPlatformAuthenticator::InvokeGetAssertionCallback(
+void WinWebAuthnApiAuthenticator::InvokeGetAssertionCallback(
     GetAssertionCallback cb,
     CtapDeviceResponseCode status,
     base::Optional<AuthenticatorGetAssertionResponse> response) {
diff --git a/device/fido/win/authenticator.h b/device/fido/win/authenticator.h
index 79ad8781..899ed90 100644
--- a/device/fido/win/authenticator.h
+++ b/device/fido/win/authenticator.h
@@ -20,15 +20,17 @@
 
 namespace device {
 
-// WinNativeCrossPlatformAuthenticator forwards WebAuthn requests to external
+// WinWebAuthnApiAuthenticator forwards WebAuthn requests to external
 // authenticators via the native Windows WebAuthentication API
 // (webauthn.dll).
-class COMPONENT_EXPORT(DEVICE_FIDO) WinNativeCrossPlatformAuthenticator
+class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator
     : public FidoAuthenticator {
  public:
-  WinNativeCrossPlatformAuthenticator(WinWebAuthnApi* win_api,
-                                      HWND current_window);
-  ~WinNativeCrossPlatformAuthenticator() override;
+  // The return value of |GetId|.
+  static const char kAuthenticatorId[];
+
+  WinWebAuthnApiAuthenticator(WinWebAuthnApi* win_api, HWND current_window);
+  ~WinWebAuthnApiAuthenticator() override;
 
   // Forces the Windows WebAuthn API not to communicate with CTAP2 devices for
   // this request. Dual-protocol devices will use U2F.
@@ -84,8 +86,8 @@
 
   GUID cancellation_id_ = {};
   base::AtomicFlag operation_cancelled_;
-  base::WeakPtrFactory<WinNativeCrossPlatformAuthenticator> weak_factory_;
-  DISALLOW_COPY_AND_ASSIGN(WinNativeCrossPlatformAuthenticator);
+  base::WeakPtrFactory<WinWebAuthnApiAuthenticator> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(WinWebAuthnApiAuthenticator);
 };
 
 }  // namespace device
diff --git a/device/fido/win/discovery.cc b/device/fido/win/discovery.cc
index 7970f6e..0e72b97 100644
--- a/device/fido/win/discovery.cc
+++ b/device/fido/win/discovery.cc
@@ -6,18 +6,17 @@
 
 namespace device {
 
-WinNativeCrossPlatformAuthenticatorDiscovery::
-    WinNativeCrossPlatformAuthenticatorDiscovery(
-        WinWebAuthnApi* const win_webauthn_api,
-        HWND parent_window)
+WinWebAuthnApiAuthenticatorDiscovery::WinWebAuthnApiAuthenticatorDiscovery(
+    WinWebAuthnApi* const win_webauthn_api,
+    HWND parent_window)
     : FidoDiscoveryBase(FidoTransportProtocol::kUsbHumanInterfaceDevice),
       win_webauthn_api_(win_webauthn_api),
       parent_window_(parent_window) {}
 
-WinNativeCrossPlatformAuthenticatorDiscovery::
-    ~WinNativeCrossPlatformAuthenticatorDiscovery() = default;
+WinWebAuthnApiAuthenticatorDiscovery::~WinWebAuthnApiAuthenticatorDiscovery() =
+    default;
 
-void WinNativeCrossPlatformAuthenticatorDiscovery::Start() {
+void WinWebAuthnApiAuthenticatorDiscovery::Start() {
   DCHECK(!authenticator_);
   if (!observer()) {
     return;
@@ -29,7 +28,7 @@
   }
 
   observer()->DiscoveryStarted(this, true /* success */);
-  authenticator_ = std::make_unique<WinNativeCrossPlatformAuthenticator>(
+  authenticator_ = std::make_unique<WinWebAuthnApiAuthenticator>(
       WinWebAuthnApi::GetDefault(), parent_window_);
   observer()->AuthenticatorAdded(this, authenticator_.get());
 }
diff --git a/device/fido/win/discovery.h b/device/fido/win/discovery.h
index b7e3d17e..ba7a803a 100644
--- a/device/fido/win/discovery.h
+++ b/device/fido/win/discovery.h
@@ -16,19 +16,18 @@
 
 // Instantiates the authenticator subclass for forwarding requests to external
 // authenticators via the Windows WebAuthn API.
-class COMPONENT_EXPORT(DEVICE_FIDO) WinNativeCrossPlatformAuthenticatorDiscovery
+class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticatorDiscovery
     : public FidoDiscoveryBase {
  public:
-  WinNativeCrossPlatformAuthenticatorDiscovery(
-      WinWebAuthnApi* const win_webauthn_api,
-      HWND parent_window);
-  ~WinNativeCrossPlatformAuthenticatorDiscovery() override;
+  WinWebAuthnApiAuthenticatorDiscovery(WinWebAuthnApi* const win_webauthn_api,
+                                       HWND parent_window);
+  ~WinWebAuthnApiAuthenticatorDiscovery() override;
 
   // FidoDiscoveryBase:
   void Start() override;
 
  private:
-  std::unique_ptr<WinNativeCrossPlatformAuthenticator> authenticator_;
+  std::unique_ptr<WinWebAuthnApiAuthenticator> authenticator_;
   WinWebAuthnApi* const win_webauthn_api_;
   const HWND parent_window_;
 };
diff --git a/docs/adding_to_third_party.md b/docs/adding_to_third_party.md
index 20f74561..693a1cf 100644
--- a/docs/adding_to_third_party.md
+++ b/docs/adding_to_third_party.md
@@ -138,7 +138,8 @@
 * Add chromium-third-party@google.com as a reviewer on your change. This
   will trigger an automatic round-robin assignment of the review to an
   appropriate reviewer. This list does not receive or deliver email, so only
-  use it as a reviewer, not for other communication.
+  use it as a reviewer, not for other communication. (Internally, see
+  b/119558132 for details about how this is configured.)
 
 Please send separate emails to the eng review and security lists.
 
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index fe736b14..2860be5 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1352,6 +1352,7 @@
   TABS_GOFORWARD = 1289,
   TABS_GOBACK = 1290,
   BRAILLEDISPLAYPRIVATE_UPDATEBLUETOOTHBRAILLEDISPLAYADDRESS = 1291,
+  AUTOTESTPRIVATE_SETASSISTANTENABLED = 1292,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/image_util.cc b/extensions/common/image_util.cc
index 9cd7bd69..21fdc66e 100644
--- a/extensions/common/image_util.cc
+++ b/extensions/common/image_util.cc
@@ -6,13 +6,18 @@
 
 #include <stddef.h>
 #include <stdint.h>
+
+#include <algorithm>
 #include <vector>
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
 #include "third_party/re2/src/re2/re2.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkCanvas.h"
@@ -176,20 +181,24 @@
   // The minimum "percent" of pixels that must be visible for the icon to be
   // considered OK.
   constexpr double kMinPercentVisiblePixels = 0.05;
-  const unsigned int total_pixels = bitmap.height() * bitmap.width();
-  unsigned int visible_pixels = 0;
+  const int total_pixels = bitmap.height() * bitmap.width();
+  // Pre-calculate the minimum number of visible pixels so we can exit early.
+  // Since we expect most icons to be visible, this will perform better for
+  // the common case.
+  const int minimum_visible_pixels =
+      std::max(kMinPercentVisiblePixels * total_pixels, 1.0);
+
+  int visible_pixels = 0;
   for (int y = 0; y < bitmap.height(); ++y) {
     for (int x = 0; x < bitmap.width(); ++x) {
       if (SkColorGetA(bitmap.getColor(x, y)) >= kAlphaThreshold) {
-        ++visible_pixels;
+        if (++visible_pixels == minimum_visible_pixels) {
+          return true;
+        }
       }
     }
   }
-  // TODO(crbug.com/805600): Add UMA stats when we move to a more
-  // sophisticated analysis of the image and the background display
-  // color.
-  return static_cast<double>(visible_pixels) / total_pixels >=
-         kMinPercentVisiblePixels;
+  return false;
 }
 
 bool IsIconAtPathSufficientlyVisible(const base::FilePath& path) {
@@ -200,8 +209,23 @@
   return IsIconSufficientlyVisible(icon);
 }
 
+struct ScopedUmaMicrosecondHistogramTimer {
+  ScopedUmaMicrosecondHistogramTimer() : timer() {}
+
+  ~ScopedUmaMicrosecondHistogramTimer() {
+    UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+        "Extensions.IsRenderedIconSufficientlyVisibleTime", timer.Elapsed(),
+        base::TimeDelta::FromMicroseconds(1), base::TimeDelta::FromSeconds(5),
+        50);
+  }
+
+  const base::ElapsedTimer timer;
+};
+
 bool IsRenderedIconSufficientlyVisible(const SkBitmap& icon,
                                        SkColor background_color) {
+  const ScopedUmaMicrosecondHistogramTimer timer;
+
   // If any of a pixel's RGB values is greater than this number, the pixel is
   // considered visible.
   constexpr unsigned int kThreshold = 15;
@@ -209,6 +233,11 @@
   // considered OK.
   constexpr double kMinPercentVisiblePixels = 0.05;
   const int total_pixels = icon.height() * icon.width();
+  // Pre-calculate the minimum number of visible pixels so we can exit early.
+  // Since we expect most icons to be visible, this will perform better for
+  // the common case.
+  const int minimum_visible_pixels =
+      std::max(kMinPercentVisiblePixels * total_pixels, 1.0);
 
   // Draw the icon onto a canvas, then draw the background color onto the
   // resulting bitmap, using SkBlendMode::kDifference. Then, check the RGB
@@ -226,12 +255,13 @@
       SkColor pixel = bitmap.getColor(x, y);
       if (SkColorGetR(pixel) > kThreshold || SkColorGetB(pixel) > kThreshold ||
           SkColorGetG(pixel) > kThreshold) {
-        ++visible_pixels;
+        if (++visible_pixels == minimum_visible_pixels) {
+          return true;
+        }
       }
     }
   }
-  return static_cast<double>(visible_pixels) / total_pixels >=
-         kMinPercentVisiblePixels;
+  return false;
 }
 
 bool IsRenderedIconAtPathSufficientlyVisible(const base::FilePath& path,
diff --git a/extensions/common/image_util_unittest.cc b/extensions/common/image_util_unittest.cc
index c2bc598..326834b 100644
--- a/extensions/common/image_util_unittest.cc
+++ b/extensions/common/image_util_unittest.cc
@@ -6,8 +6,10 @@
 
 #include "base/files/file_path.h"
 #include "base/path_service.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "extensions/common/extension_paths.h"
 #include "extensions/common/image_util.h"
+#include "extensions/test/logging_timer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -184,7 +186,10 @@
   base::FilePath test_dir;
   ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_dir));
   base::FilePath icon_path;
+  const std::string metric_name =
+      "Extensions.IsRenderedIconSufficientlyVisibleTime";
   {
+    base::HistogramTester histogram_tester;
     // This icon has all transparent pixels, so it will fail.
     icon_path = test_dir.AppendASCII("transparent_icon.png");
     SkBitmap transparent_icon;
@@ -192,8 +197,10 @@
     EXPECT_FALSE(image_util::IsIconSufficientlyVisible(transparent_icon));
     EXPECT_FALSE(image_util::IsRenderedIconSufficientlyVisible(transparent_icon,
                                                                SK_ColorWHITE));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with an icon that has one opaque pixel.
     icon_path = test_dir.AppendASCII("one_pixel_opaque_icon.png");
     SkBitmap visible_icon;
@@ -201,8 +208,10 @@
     EXPECT_FALSE(image_util::IsIconSufficientlyVisible(visible_icon));
     EXPECT_FALSE(image_util::IsRenderedIconSufficientlyVisible(visible_icon,
                                                                SK_ColorWHITE));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with an icon that has one transparent pixel.
     icon_path = test_dir.AppendASCII("one_pixel_transparent_icon.png");
     SkBitmap visible_icon;
@@ -210,8 +219,10 @@
     EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon));
     EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon,
                                                               SK_ColorWHITE));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with an icon that is completely opaque.
     icon_path = test_dir.AppendASCII("opaque_icon.png");
     SkBitmap visible_icon;
@@ -219,8 +230,10 @@
     EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon));
     EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon,
                                                               SK_ColorWHITE));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with an icon that is rectangular.
     icon_path = test_dir.AppendASCII("rectangle.png");
     SkBitmap visible_icon;
@@ -228,8 +241,10 @@
     EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon));
     EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon,
                                                               SK_ColorWHITE));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with a solid color icon that is completely opaque. Use the icon's
     // color as the background color in the call to analyze its visibility.
     // It should be invisible in this case.
@@ -239,8 +254,10 @@
     const SkColor pixel_color = solid_icon.getColor(0, 0);
     EXPECT_FALSE(
         image_util::IsRenderedIconSufficientlyVisible(solid_icon, pixel_color));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
   {
+    base::HistogramTester histogram_tester;
     // Test with a two-color icon that is completely opaque. Use one of the
     // icon's colors as the background color in the call to analyze its
     // visibility. It should be visible in this case.
@@ -250,7 +267,52 @@
     const SkColor pixel_color = two_color_icon.getColor(0, 0);
     EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(two_color_icon,
                                                               pixel_color));
+    histogram_tester.ExpectTotalCount(metric_name, 1);
   }
 }
 
+TEST(ImageUtilTest, MANUAL_IsIconSufficientlyVisiblePerfTest) {
+  base::FilePath test_dir;
+  ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_dir));
+  base::FilePath icon_path;
+  // This icon has all transparent pixels.
+  icon_path = test_dir.AppendASCII("transparent_icon.png");
+  SkBitmap invisible_icon;
+  ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &invisible_icon));
+  // This icon is completely opaque.
+  icon_path = test_dir.AppendASCII("opaque_icon.png");
+  SkBitmap visible_icon;
+  ASSERT_TRUE(image_util::LoadPngFromFile(icon_path, &visible_icon));
+
+  static constexpr char kInvisibleTimerId[] = "InvisibleIcon";
+  static constexpr char kVisibleTimerId[] = "VisibleIcon";
+  static constexpr char kInvisibleRenderedTimerId[] = "InvisibleRenderedIcon";
+  static constexpr char kVisibleRenderedTimerId[] = "VisibleRenderedIcon";
+  constexpr int kIterations = 100000;
+
+  for (int i = 0; i < kIterations; ++i) {
+    LoggingTimer timer(kInvisibleTimerId);
+    EXPECT_FALSE(image_util::IsIconSufficientlyVisible(invisible_icon));
+  }
+
+  for (int i = 0; i < kIterations; ++i) {
+    LoggingTimer timer(kVisibleTimerId);
+    EXPECT_TRUE(image_util::IsIconSufficientlyVisible(visible_icon));
+  }
+
+  for (int i = 0; i < kIterations; ++i) {
+    LoggingTimer timer(kInvisibleRenderedTimerId);
+    EXPECT_FALSE(image_util::IsRenderedIconSufficientlyVisible(invisible_icon,
+                                                               SK_ColorWHITE));
+  }
+
+  for (int i = 0; i < kIterations; ++i) {
+    LoggingTimer timer(kVisibleRenderedTimerId);
+    EXPECT_TRUE(image_util::IsRenderedIconSufficientlyVisible(visible_icon,
+                                                              SK_ColorWHITE));
+  }
+
+  LoggingTimer::Print();
+}
+
 }  // namespace extensions
diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc
index c1d0063c..0d5bceef 100644
--- a/gpu/command_buffer/client/gles2_implementation.cc
+++ b/gpu/command_buffer/client/gles2_implementation.cc
@@ -1657,15 +1657,15 @@
                                                   GLsizei count,
                                                   const char* const* names,
                                                   GLuint* indices) {
+  if (!PackStringsToBucket(count, names, nullptr, "glGetUniformIndices")) {
+    return false;
+  }
   typedef cmds::GetUniformIndices::Result Result;
   Result* result = GetResultAs<Result*>();
   if (!result) {
     return false;
   }
   result->SetNumResults(0);
-  if (!PackStringsToBucket(count, names, nullptr, "glGetUniformIndices")) {
-    return false;
-  }
   helper_->GetUniformIndices(program, kResultBucketId, GetResultShmId(),
                              GetResultShmOffset());
   WaitForCmd();
@@ -3285,7 +3285,8 @@
   helper_->GetActiveAttrib(program, index, kResultBucketId, GetResultShmId(),
                            GetResultShmOffset());
   WaitForCmd();
-  if (result->success) {
+  bool success = !!result->success;
+  if (success) {
     if (size) {
       *size = result->size;
     }
@@ -3294,6 +3295,7 @@
     }
     if (length || name) {
       std::vector<int8_t> str;
+      // Note: this can invalidate |result|.
       GetBucketContents(kResultBucketId, &str);
       GLsizei max_size =
           std::min(static_cast<size_t>(bufsize) - 1,
@@ -3307,7 +3309,7 @@
       }
     }
   }
-  return result->success != 0;
+  return success;
 }
 
 void GLES2Implementation::GetActiveAttrib(GLuint program,
@@ -3545,12 +3547,6 @@
                                                     const GLuint* indices,
                                                     GLenum pname,
                                                     GLint* params) {
-  typedef cmds::GetActiveUniformsiv::Result Result;
-  Result* result = GetResultAs<Result*>();
-  if (!result) {
-    return false;
-  }
-  result->SetNumResults(0);
   base::CheckedNumeric<size_t> bytes = static_cast<size_t>(count);
   bytes *= sizeof(GLuint);
   if (!bytes.IsValid()) {
@@ -3558,6 +3554,12 @@
     return false;
   }
   SetBucketContents(kResultBucketId, indices, bytes.ValueOrDefault(0));
+  typedef cmds::GetActiveUniformsiv::Result Result;
+  Result* result = GetResultAs<Result*>();
+  if (!result) {
+    return false;
+  }
+  result->SetNumResults(0);
   helper_->GetActiveUniformsiv(program, kResultBucketId, pname,
                                GetResultShmId(), GetResultShmOffset());
   WaitForCmd();
diff --git a/ios/build/bots/chromium.fyi/ios-slimnav.json b/ios/build/bots/chromium.fyi/ios-slimnav.json
index 1357a81c..78a6a7c 100644
--- a/ios/build/bots/chromium.fyi/ios-slimnav.json
+++ b/ios/build/bots/chromium.fyi/ios-slimnav.json
@@ -141,12 +141,14 @@
       "priority": 29
     },
     {
-      "include": "eg_tests.json",
+      "app": "ios_chrome_bookmarks_egtests",
       "test args": [
         "--enable-features=SlimNavigationManager"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
+      "shard size": 2,
+      "xctest": true,
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -155,12 +157,120 @@
       "priority": 29
     },
     {
-      "include": "eg_tests.json",
+      "app": "ios_chrome_manual_fill_egtests",
+      "test args": [
+        "--enable-features=AutofillManualFallback,WebFrameMessaging,SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_web_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_settings_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_reading_list_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_showcase_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_bookmarks_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "shard size": 2,
+      "device type": "iPad Air 2",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_manual_fill_egtests",
+      "test args": [
+        "--enable-features=AutofillManualFallback,WebFrameMessaging,SlimNavigationManager"
+      ],
+      "xctest": true,
+      "device type": "iPad Air 2",
+      "os": "12.1",
+
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_web_egtests",
       "test args": [
         "--enable-features=SlimNavigationManager"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
+      "xctest": true,
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
@@ -169,10 +279,72 @@
       "priority": 29
     },
     {
-      "include": "eg_tests.json",
+      "app": "ios_chrome_settings_egtests",
       "test args": [
         "--enable-features=SlimNavigationManager"
       ],
+      "device type": "iPad Air 2",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_reading_list_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_showcase_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPad Air 2",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_bookmarks_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "11.4",
+      "shard size": 2,
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_manual_fill_egtests",
+      "test args": [
+        "--enable-features=AutofillManualFallback,WebFrameMessaging,SlimNavigationManager"
+      ],
+      "xctest": true,
       "device type": "iPhone X",
       "os": "11.4",
       "dimensions": [
@@ -183,12 +355,149 @@
       "priority": 29
     },
     {
-      "include": "eg_tests.json",
+      "app": "ios_chrome_web_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_settings_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_reading_list_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_showcase_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "11.4",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_bookmarks_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "shard size": 2,
+      "device type": "iPhone X",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_manual_fill_egtests",
+      "test args": [
+        "--enable-features=AutofillManualFallback,WebFrameMessaging,SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_web_egtests",
       "test args": [
         "--enable-features=SlimNavigationManager"
       ],
       "device type": "iPhone X",
       "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_settings_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_chrome_reading_list_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "12.1",
+      "xctest": true,
+      "dimensions": [
+        { "os": "Mac-10.13.4", "pool": "Chrome" },
+        { "os": "Mac-10.13.5", "pool": "Chrome" },
+        { "os": "Mac-10.13.6", "pool": "Chrome" }
+      ],
+      "priority": 29
+    },
+    {
+      "app": "ios_showcase_egtests",
+      "test args": [
+        "--enable-features=SlimNavigationManager"
+      ],
+      "device type": "iPhone X",
+      "os": "12.1",
+      "xctest": true,
       "dimensions": [
         { "os": "Mac-10.13.4", "pool": "Chrome" },
         { "os": "Mac-10.13.5", "pool": "Chrome" },
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index fb32f26..aabc70eb 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -1768,11 +1768,6 @@
   return [_browserViewWrangler mainBVC];
 }
 
-- (void)setMainBVC:(BrowserViewController*)mainBVC {
-  DCHECK(_browserViewWrangler);
-  [_browserViewWrangler setMainBVC:mainBVC];
-}
-
 - (TabModel*)mainTabModel {
   DCHECK(_browserViewWrangler);
   return [_browserViewWrangler mainTabModel];
@@ -1788,11 +1783,6 @@
   return [_browserViewWrangler otrBVC];
 }
 
-- (void)setOtrBVC:(BrowserViewController*)otrBVC {
-  DCHECK(_browserViewWrangler);
-  [_browserViewWrangler setOtrBVC:otrBVC];
-}
-
 - (TabModel*)otrTabModel {
   DCHECK(_browserViewWrangler);
   return [_browserViewWrangler otrTabModel];
@@ -2299,29 +2289,9 @@
     (NTPTabOpeningPostOpeningAction)action {
   switch (action) {
     case START_VOICE_SEARCH:
-      if (@available(iOS 11, *)) {
-        return ^{
-          [self startVoiceSearchInCurrentBVC];
-        };
-      } else {
-        return ^{
-          // On iOS10.3.X, the launching the application using an external URL
-          // sometimes triggers notifications
-          // applicationDidBecomeActive
-          // applicationWillResignActive
-          // applicationDidBecomeActive.
-          // Triggering voiceSearch immediatley will cause its dismiss on
-          // applicationWillResignActive.
-          // Add a timer here and hope this will be enough so that voice search
-          // is triggered after second applicationDidBecomeActive.
-          // TODO(crbug.com/766951): remove this workaround.
-          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC),
-                         dispatch_get_main_queue(), ^{
-                           [self startVoiceSearchInCurrentBVC];
-                         });
-
-        };
-      }
+      return ^{
+        [self startVoiceSearchInCurrentBVC];
+      };
     case START_QR_CODE_SCANNER:
       return ^{
         [self.currentBVC.dispatcher showQRScanner];
diff --git a/ios/chrome/browser/autofill/autofill_controller_unittest.mm b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
index 1f1f3b5..03747a1 100644
--- a/ios/chrome/browser/autofill/autofill_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
@@ -558,13 +558,6 @@
 // Checks that focusing on and typing on one field, then changing focus before
 // typing again, result in suggestions.
 TEST_F(AutofillControllerTest, KeyValueFocusChange) {
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/836808): This test hangs on iOS10 devices when there are
-    // no breakpoint.
-    return;
-  }
-#endif
   SetUpKeyValueData();
 
   // Focus the dummy field and confirm no suggestions are presented.
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view.mm b/ios/chrome/browser/autofill/form_input_accessory_view.mm
index 08a5fb3..cb7520d 100644
--- a/ios/chrome/browser/autofill/form_input_accessory_view.mm
+++ b/ios/chrome/browser/autofill/form_input_accessory_view.mm
@@ -150,7 +150,7 @@
   trailingView.translatesAutoresizingMaskIntoConstraints = NO;
   [self addSubview:trailingView];
 
-  id<LayoutGuideProvider> layoutGuide = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> layoutGuide = self.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     [leadingViewContainer.topAnchor
         constraintEqualToAnchor:layoutGuide.topAnchor],
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
index bb5885f1..df20ca6 100644
--- a/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_controller.mm
@@ -281,15 +281,6 @@
   // On ipad we hide the views so they don't stick around at the bottom. Only
   // needed on iPad because we add the view directly to the keyboard view.
   if (IsIPadIdiom() && self.customAccessoryView) {
-    if (@available(iOS 11, *)) {
-    } else {
-      // [iPad iOS 10] There is a bug when constraining something to the
-      // keyboard view. So this updates the frame instead.
-      CGFloat height = autofill::kInputAccessoryHeight;
-      self.customAccessoryView.frame =
-          CGRectMake(keyboardView.frame.origin.x, -height,
-                     keyboardView.frame.size.width, height);
-    }
     if (CGRectEqualToRect(_keyboardFrame, CGRectZero)) {
       self.customAccessoryView.hidden = true;
       self.grayBackgroundView.hidden = true;
@@ -318,28 +309,18 @@
   if (self.customAccessoryView && !self.customAccessoryView.superview) {
     if (IsIPadIdiom()) {
       UIView* keyboardView = [self getKeyboardView];
-      // [iPad iOS 10] There is a bug when constraining something to the
-      // keyboard view. So this sets the frame instead.
-      if (@available(iOS 11, *)) {
-        self.customAccessoryView.translatesAutoresizingMaskIntoConstraints = NO;
-        [keyboardView addSubview:self.customAccessoryView];
-        [NSLayoutConstraint activateConstraints:@[
-          [self.customAccessoryView.leadingAnchor
-              constraintEqualToAnchor:keyboardView.leadingAnchor],
-          [self.customAccessoryView.trailingAnchor
-              constraintEqualToAnchor:keyboardView.trailingAnchor],
-          [self.customAccessoryView.bottomAnchor
-              constraintEqualToAnchor:keyboardView.topAnchor],
-          [self.customAccessoryView.heightAnchor
-              constraintEqualToConstant:autofill::kInputAccessoryHeight]
-        ]];
-      } else {
-        CGFloat height = autofill::kInputAccessoryHeight;
-        self.customAccessoryView.frame =
-            CGRectMake(keyboardView.frame.origin.x, -height,
-                       keyboardView.frame.size.width, height);
-        [keyboardView addSubview:self.customAccessoryView];
-      }
+      self.customAccessoryView.translatesAutoresizingMaskIntoConstraints = NO;
+      [keyboardView addSubview:self.customAccessoryView];
+      [NSLayoutConstraint activateConstraints:@[
+        [self.customAccessoryView.leadingAnchor
+            constraintEqualToAnchor:keyboardView.leadingAnchor],
+        [self.customAccessoryView.trailingAnchor
+            constraintEqualToAnchor:keyboardView.trailingAnchor],
+        [self.customAccessoryView.bottomAnchor
+            constraintEqualToAnchor:keyboardView.topAnchor],
+        [self.customAccessoryView.heightAnchor
+            constraintEqualToConstant:autofill::kInputAccessoryHeight]
+      ]];
       if (!self.grayBackgroundView.superview) {
         [keyboardView addSubview:self.grayBackgroundView];
         [keyboardView sendSubviewToBack:self.grayBackgroundView];
diff --git a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher.mm b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher.mm
index 8e53bdc..24a743c 100644
--- a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher.mm
+++ b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher.mm
@@ -15,14 +15,44 @@
 #error "This file requires ARC support."
 #endif
 
-@interface PasswordFetcher ()<SavePasswordsConsumerDelegate> {
+// Protocol to observe changes on the Password Store.
+@protocol PasswordStoreObserver<NSObject>
+
+// The logins in the Password Store changed.
+- (void)loginsDidChange;
+
+@end
+
+namespace {
+
+// Objective-C bridge to observe changes in the Password Store.
+class PasswordStoreObserverBridge
+    : public password_manager::PasswordStore::Observer {
+ public:
+  explicit PasswordStoreObserverBridge(id<PasswordStoreObserver> observer)
+      : observer_(observer) {}
+
+  PasswordStoreObserverBridge() {}
+
+ private:
+  void OnLoginsChanged(
+      const password_manager::PasswordStoreChangeList& changes) override {
+    [observer_ loginsDidChange];
+  }
+  __weak id<PasswordStoreObserver> observer_ = nil;
+};
+
+}  // namespace
+
+@interface PasswordFetcher ()<SavePasswordsConsumerDelegate,
+                              PasswordStoreObserver> {
   // The interface for getting and manipulating a user's saved passwords.
   scoped_refptr<password_manager::PasswordStore> _passwordStore;
   // A helper object for passing data about saved passwords from a finished
   // password store request to the SavePasswordsCollectionViewController.
   std::unique_ptr<ios::SavePasswordsConsumer> _savedPasswordsConsumer;
-  // The list of the user's saved passwords.
-  std::vector<std::unique_ptr<autofill::PasswordForm>> _savedForms;
+  // The object to observe changes in the Password Store.
+  std::unique_ptr<PasswordStoreObserverBridge> _passwordStoreObserver;
 }
 
 // Delegate to send the fetchted passwords.
@@ -48,23 +78,37 @@
     _passwordStore = passwordStore;
     _savedPasswordsConsumer.reset(new ios::SavePasswordsConsumer(self));
     _passwordStore->GetAutofillableLogins(_savedPasswordsConsumer.get());
+    _passwordStoreObserver.reset(new PasswordStoreObserverBridge(self));
+    _passwordStore->AddObserver(_passwordStoreObserver.get());
   }
   return self;
 }
 
+- (void)dealloc {
+  _passwordStore->RemoveObserver(_passwordStoreObserver.get());
+}
+
 #pragma mark - SavePasswordsConsumerDelegate
 
 - (void)onGetPasswordStoreResults:
     (std::vector<std::unique_ptr<autofill::PasswordForm>>&)result {
-  for (auto it = result.begin(); it != result.end(); ++it) {
-    if (!(*it)->blacklisted_by_user)
-      _savedForms.push_back(std::move(*it));
-  }
+  result.erase(
+      std::remove_if(result.begin(), result.end(),
+                     [](std::unique_ptr<autofill::PasswordForm>& form) {
+                       return form->blacklisted_by_user;
+                     }),
+      result.end());
 
   password_manager::DuplicatesMap savedPasswordDuplicates;
-  password_manager::SortEntriesAndHideDuplicates(&_savedForms,
+  password_manager::SortEntriesAndHideDuplicates(&result,
                                                  &savedPasswordDuplicates);
-  [self.delegate passwordFetcher:self didFetchPasswords:_savedForms];
+  [self.delegate passwordFetcher:self didFetchPasswords:result];
+}
+
+#pragma mark - PasswordStoreObserver
+
+- (void)loginsDidChange {
+  _passwordStore->GetAutofillableLogins(_savedPasswordsConsumer.get());
 }
 
 @end
diff --git a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
index 5afd173a..79a67fb5 100644
--- a/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
+++ b/ios/chrome/browser/autofill/manual_fill/passwords_fetcher_unittest.mm
@@ -74,24 +74,26 @@
         .get();
   }
 
-  // Creates and adds a saved password form.
-  void AddSavedForm1() {
-    auto form = std::make_unique<autofill::PasswordForm>();
-    form->origin = GURL("http://www.example.com/accounts/LoginAuth");
-    form->action = GURL("http://www.example.com/accounts/Login");
-    form->username_element = base::ASCIIToUTF16("Email");
-    form->username_value = base::ASCIIToUTF16("test@egmail.com");
-    form->password_element = base::ASCIIToUTF16("Passwd");
-    form->password_value = base::ASCIIToUTF16("test");
-    form->submit_element = base::ASCIIToUTF16("signIn");
-    form->signon_realm = "http://www.example.com/";
-    form->preferred = false;
-    form->scheme = autofill::PasswordForm::SCHEME_HTML;
-    form->blacklisted_by_user = false;
-    GetPasswordStore()->AddLogin(*std::move(form));
+  autofill::PasswordForm Form1() {
+    autofill::PasswordForm form;
+    form.origin = GURL("http://www.example.com/accounts/LoginAuth");
+    form.action = GURL("http://www.example.com/accounts/Login");
+    form.username_element = base::ASCIIToUTF16("Email");
+    form.username_value = base::ASCIIToUTF16("test@egmail.com");
+    form.password_element = base::ASCIIToUTF16("Passwd");
+    form.password_value = base::ASCIIToUTF16("test");
+    form.submit_element = base::ASCIIToUTF16("signIn");
+    form.signon_realm = "http://www.example.com/";
+    form.preferred = false;
+    form.scheme = autofill::PasswordForm::SCHEME_HTML;
+    form.blacklisted_by_user = false;
+    return form;
   }
 
   // Creates and adds a saved password form.
+  void AddSavedForm1() { GetPasswordStore()->AddLogin(Form1()); }
+
+  // Creates and adds a saved password form.
   void AddSavedForm2() {
     auto form = std::make_unique<autofill::PasswordForm>();
     form->origin = GURL("http://www.example2.com/accounts/LoginAuth");
@@ -227,4 +229,32 @@
   EXPECT_TRUE(passwordFetcher);
 }
 
+// Tests PasswordFetcher receives 0 passwords.
+TEST_F(PasswordFetcherTest, ReceivesZeroPasswords) {
+  AddSavedForm1();
+  TestPasswordFetcherDelegate* passwordFetcherDelegate =
+      [[TestPasswordFetcherDelegate alloc] init];
+  auto passwordStore = IOSChromePasswordStoreFactory::GetForBrowserState(
+      chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS);
+  PasswordFetcher* passwordFetcher =
+      [[PasswordFetcher alloc] initWithPasswordStore:passwordStore
+                                            delegate:passwordFetcherDelegate];
+  WaitUntilCondition(
+      ^bool {
+        return passwordFetcherDelegate.passwordNumber > 0;
+      },
+      true, base::TimeDelta::FromSeconds(1000));
+  EXPECT_EQ(passwordFetcherDelegate.passwordNumber, 1u);
+
+  GetPasswordStore()->RemoveLogin(Form1());
+
+  WaitUntilCondition(
+      ^bool {
+        return passwordFetcherDelegate.passwordNumber == 0;
+      },
+      true, base::TimeDelta::FromSeconds(1000));
+  EXPECT_EQ(passwordFetcherDelegate.passwordNumber, 0u);
+  EXPECT_TRUE(passwordFetcher);
+}
+
 }  // namespace
diff --git a/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm b/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm
index b99f7f6..8577156 100644
--- a/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm
+++ b/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm
@@ -114,7 +114,7 @@
 
   // When tab 3 is closed, tab 2 is front and Handoff URL should be the URL for
   // tab 2.
-  chrome_test_util::CloseCurrentTab();
+  [ChromeEarlGrey closeCurrentTab];
   AssertHandoffURL(tab2URL);
 
   // Switches back to the first tab.
diff --git a/ios/chrome/browser/drag_and_drop/drop_and_navigate_interaction_unittest.mm b/ios/chrome/browser/drag_and_drop/drop_and_navigate_interaction_unittest.mm
index ea0fb4a..e6393e2 100644
--- a/ios/chrome/browser/drag_and_drop/drop_and_navigate_interaction_unittest.mm
+++ b/ios/chrome/browser/drag_and_drop/drop_and_navigate_interaction_unittest.mm
@@ -17,11 +17,9 @@
 using DropAndNavigateTest = PlatformTest;
 
 TEST_F(DropAndNavigateTest, Instantiation) {
-  if (@available(iOS 11, *)) {
-    DropAndNavigateInteraction* interaction =
-        [[DropAndNavigateInteraction alloc] initWithDelegate:nil];
-    DCHECK(interaction.delegate);
-  }
+  DropAndNavigateInteraction* interaction =
+      [[DropAndNavigateInteraction alloc] initWithDelegate:nil];
+  DCHECK(interaction.delegate);
 }
 
 }  // namespace
diff --git a/ios/chrome/browser/metrics/drag_and_drop_recorder.mm b/ios/chrome/browser/metrics/drag_and_drop_recorder.mm
index 44d470082..690a7dcf 100644
--- a/ios/chrome/browser/metrics/drag_and_drop_recorder.mm
+++ b/ios/chrome/browser/metrics/drag_and_drop_recorder.mm
@@ -64,12 +64,10 @@
 - (instancetype)initWithView:(UIView*)view {
   self = [super init];
   if (self) {
-    if (@available(iOS 11, *)) {
-      dropSessions_ = [NSHashTable weakObjectsHashTable];
-      UIDropInteraction* dropInteraction =
-          [[UIDropInteraction alloc] initWithDelegate:self];
-      [view addInteraction:dropInteraction];
-    }
+    dropSessions_ = [NSHashTable weakObjectsHashTable];
+    UIDropInteraction* dropInteraction =
+        [[UIDropInteraction alloc] initWithDelegate:self];
+    [view addInteraction:dropInteraction];
   }
   return self;
 }
diff --git a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
index 38da8aa..f7558526 100644
--- a/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
+++ b/ios/chrome/browser/metrics/tab_usage_recorder_egtest.mm
@@ -94,11 +94,7 @@
 
 // Opens 2 new tabs with different URLs.
 void OpenTwoTabs() {
-  chrome_test_util::CloseAllTabsInCurrentMode();
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeAllTabsInCurrentMode];
   const GURL url1 = web::test::HttpServer::MakeUrl(kTestUrl1);
   const GURL url2 = web::test::HttpServer::MakeUrl(kTestUrl2);
   NewMainTabWithURL(url1, kURL1FirstWord);
@@ -179,10 +175,7 @@
 
   // This test opens three tabs.
   const int numberOfTabs = 3;
-  chrome_test_util::CloseAllTabsInCurrentMode();
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+  [ChromeEarlGrey closeAllTabsInCurrentMode];
 
   // Open three tabs with http:// urls.
   for (NSUInteger i = 0; i < numberOfTabs; i++) {
@@ -345,11 +338,7 @@
     GREYFail(error);
   };
 
-  chrome_test_util::CloseAllTabsInCurrentMode();
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeAllTabsInCurrentMode];
   GURL URL = web::test::HttpServer::MakeUrl(kTestUrl1);
   NewMainTabWithURL(URL, kURL1FirstWord);
   OpenNewIncognitoTabUsingUIAndEvictMainTabs();
diff --git a/ios/chrome/browser/metrics/ukm_egtest.mm b/ios/chrome/browser/metrics/ukm_egtest.mm
index 04b9067..94b7a60 100644
--- a/ios/chrome/browser/metrics/ukm_egtest.mm
+++ b/ios/chrome/browser/metrics/ukm_egtest.mm
@@ -151,12 +151,12 @@
 
 void CloseCurrentIncognitoTab() {
   NSUInteger incognito_tab_count = GetIncognitoTabCount();
-  chrome_test_util::CloseCurrentTab();
+  [ChromeEarlGrey closeCurrentTab];
   [ChromeEarlGrey waitForIncognitoTabCount:(incognito_tab_count - 1)];
 }
 
 void CloseAllIncognitoTabs() {
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [ChromeEarlGrey closeAllIncognitoTabs];
   [ChromeEarlGrey waitForIncognitoTabCount:0];
 
   // The user is dropped into the tab grid after closing the last incognito tab.
@@ -302,7 +302,7 @@
   OpenNewRegularTab();
   AssertUKMEnabled(false);
 
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [ChromeEarlGrey closeAllIncognitoTabs];
   [ChromeEarlGrey waitForIncognitoTabCount:0];
   AssertUKMEnabled(true);
 
diff --git a/ios/chrome/browser/net/cookie_util.mm b/ios/chrome/browser/net/cookie_util.mm
index 65130370..7582d21 100644
--- a/ios/chrome/browser/net/cookie_util.mm
+++ b/ios/chrome/browser/net/cookie_util.mm
@@ -97,13 +97,11 @@
 
   // On iOS 11, there is no need to use PersistentCookieStore or CookieMonster
   // because there is a way to access cookies in WKHTTPCookieStore. This will
-  // allow URLFetcher and anyother users of net:CookieStore to in iOS to set
+  // allow URLFetcher and any other users of net:CookieStore to in iOS to set
   // and get cookies directly in WKHTTPCookieStore.
-  if (@available(iOS 11, *)) {
-    if (base::FeatureList::IsEnabled(web::features::kWKHTTPSystemCookieStore)) {
-      return std::make_unique<net::CookieStoreIOS>(
-          std::move(system_cookie_store), net_log);
-    }
+  if (base::FeatureList::IsEnabled(web::features::kWKHTTPSystemCookieStore)) {
+    return std::make_unique<net::CookieStoreIOS>(std::move(system_cookie_store),
+                                                 net_log);
   }
 
   scoped_refptr<net::SQLitePersistentCookieStore> persistent_store = nullptr;
diff --git a/ios/chrome/browser/net/cookie_util_unittest.mm b/ios/chrome/browser/net/cookie_util_unittest.mm
index d28fdd6f..6e95565 100644
--- a/ios/chrome/browser/net/cookie_util_unittest.mm
+++ b/ios/chrome/browser/net/cookie_util_unittest.mm
@@ -116,17 +116,10 @@
     return callback_called;
   }));
 
-  if (@available(iOS 11, *)) {
-    // When WKHTTPSystemCookieStore feature is enabled and the iOS version is
-    // 11+ the cookie should be set directly in the backing SystemCookieStore.
-    EXPECT_EQ(1U, result_cookies.count);
-    EXPECT_NSEQ(cookie_name, result_cookies[0].name);
-    EXPECT_NSEQ(cookie_value, result_cookies[0].value);
-  } else {
-    // Before iOS 11, cookies are not set in the backing SystemCookieStore
-    // instead they are found on CookieMonster.
-    EXPECT_EQ(0U, result_cookies.count);
-  }
+  // The cookie should be set directly in the backing SystemCookieStore.
+  EXPECT_EQ(1U, result_cookies.count);
+  EXPECT_NSEQ(cookie_name, result_cookies[0].name);
+  EXPECT_NSEQ(cookie_value, result_cookies[0].value);
 
   // Clear cookies that was set in the test.
   __block bool cookies_cleared = false;
diff --git a/ios/chrome/browser/net/cookies_egtest.mm b/ios/chrome/browser/net/cookies_egtest.mm
index d7fb763..ebbf929 100644
--- a/ios/chrome/browser/net/cookies_egtest.mm
+++ b/ios/chrome/browser/net/cookies_egtest.mm
@@ -133,11 +133,7 @@
 
   // Finally, closes all incognito tabs while still in normal tab.
   // Checks that incognito cookie is gone.
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // incognito tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeAllIncognitoTabs];
   [ChromeEarlGrey openNewTab];
   [ChromeEarlGrey
       loadURL:web::test::HttpServer::MakeUrl(kTestUrlIncognitoBrowsing)];
@@ -165,11 +161,7 @@
                   @"Only one cookie should be found in incognito mode.");
 
   // Closes all incognito tabs and switch back to a normal tab.
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // incognito tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeAllIncognitoTabs];
   [ChromeEarlGrey openNewTab];
   [ChromeEarlGrey
       loadURL:web::test::HttpServer::MakeUrl(kTestUrlNormalBrowsing)];
@@ -214,11 +206,7 @@
 
   // Closes all incognito tabs and then switching back to a normal tab. Verifies
   // that the cookie set earlier is still there.
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close all
-  // incognito tabs and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeAllIncognitoTabs];
   [ChromeEarlGrey openNewTab];
   [ChromeEarlGrey
       loadURL:web::test::HttpServer::MakeUrl(kTestUrlNormalBrowsing)];
diff --git a/ios/chrome/browser/passwords/save_passwords_consumer.mm b/ios/chrome/browser/passwords/save_passwords_consumer.mm
index bd472f3..904c0c7 100644
--- a/ios/chrome/browser/passwords/save_passwords_consumer.mm
+++ b/ios/chrome/browser/passwords/save_passwords_consumer.mm
@@ -18,8 +18,7 @@
 
 void SavePasswordsConsumer::OnGetPasswordStoreResults(
     std::vector<std::unique_ptr<autofill::PasswordForm>> results) {
-  if (!results.empty())
-    [delegate_ onGetPasswordStoreResults:results];
+  [delegate_ onGetPasswordStoreResults:results];
 }
 
 }  // namespace ios
diff --git a/ios/chrome/browser/prerender/prerender_egtest.mm b/ios/chrome/browser/prerender/prerender_egtest.mm
index 04c3892..91ab237 100644
--- a/ios/chrome/browser/prerender/prerender_egtest.mm
+++ b/ios/chrome/browser/prerender/prerender_egtest.mm
@@ -53,13 +53,11 @@
 
 // Test that tapping the prerendered suggestions opens it.
 - (void)testTapPrerenderSuggestions {
-  // TODO(crbug.com/793306): Re-enable the test on iOS 11 iPad once the
-  // alternate letters problem is fixed.
+  // TODO(crbug.com/793306): Re-enable the test on iPad once the alternate
+  // letters problem is fixed.
   if (IsIPadIdiom()) {
-    if (@available(iOS 11, *)) {
-      EARL_GREY_TEST_DISABLED(
-          @"Disabled for iPad due to alternate letters educational screen.");
-    }
+    EARL_GREY_TEST_DISABLED(
+        @"Disabled for iPad due to alternate letters educational screen.");
   }
 
   GREYAssertTrue(chrome_test_util::ClearBrowsingHistory(),
diff --git a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
index 1a081c4..e2e7b30 100644
--- a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
+++ b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
@@ -362,8 +362,7 @@
   // a network request with cookies sent and saved is by making it through a
   // WKWebView.
   SetPendingFetch(true);
-  bool shouldUseXmlHTTPRequest =
-      IsMultiloginUrl(gaia_gurl) || !base::ios::IsRunningOnIOS11OrLater();
+  bool shouldUseXmlHTTPRequest = IsMultiloginUrl(gaia_gurl);
   bridge_->Fetch(gaia_gurl, headers, body, shouldUseXmlHTTPRequest);
 }
 
diff --git a/ios/chrome/browser/signin/gaia_auth_fetcher_ios_unittest.mm b/ios/chrome/browser/signin/gaia_auth_fetcher_ios_unittest.mm
index 5f3d687..00e2413 100644
--- a/ios/chrome/browser/signin/gaia_auth_fetcher_ios_unittest.mm
+++ b/ios/chrome/browser/signin/gaia_auth_fetcher_ios_unittest.mm
@@ -106,15 +106,8 @@
       GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
   EXPECT_CALL(consumer_, OnClientLoginFailure(expected_error)).Times(1);
 
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    [static_cast<WKWebView*>([GetMockWKWebView() expect])
-        loadRequest:[OCMArg any]];
-  } else {
-    // TODO(crbug.com/740987): Remove this code once iOS 10 is dropped.
-    [static_cast<WKWebView*>([GetMockWKWebView() expect])
-        loadHTMLString:[OCMArg any]
-               baseURL:[OCMArg any]];
-  }
+  [static_cast<WKWebView*>([GetMockWKWebView() expect])
+      loadRequest:[OCMArg any]];
   [[GetMockWKWebView() expect] stopLoading];
 
   gaia_auth_fetcher_->StartOAuthLogin("fake_token", "gaia");
diff --git a/ios/chrome/browser/snapshots/snapshot_tab_helper.mm b/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
index 560d6ae..7af220e 100644
--- a/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
+++ b/ios/chrome/browser/snapshots/snapshot_tab_helper.mm
@@ -129,13 +129,10 @@
 
 void SnapshotTabHelper::UpdateSnapshotWithCallback(void (^callback)(UIImage*)) {
   if (IsWKWebViewSnapshotsEnabled() && web_state_->ContentIsHTML()) {
-    if (@available(iOS 11, *)) {
-      [snapshot_generator_ updateWebViewSnapshotWithCompletion:callback];
-      return;
-    }
+    [snapshot_generator_ updateWebViewSnapshotWithCompletion:callback];
+    return;
   }
-  // Pre-iOS 11 and native content cannot utilize the WKWebView snapshotting
-  // API.
+  // Native content cannot utilize the WKWebView snapshotting API.
   UIImage* image =
       UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
   dispatch_async(dispatch_get_main_queue(), ^{
@@ -209,17 +206,14 @@
   if (!ignore_next_load_ && !pause_snapshotting_ &&
       load_completion_status == web::PageLoadCompletionStatus::SUCCESS) {
     if (IsWKWebViewSnapshotsEnabled() && web_state->ContentIsHTML()) {
-      if (@available(iOS 11, *)) {
-        base::PostDelayedTaskWithTraits(
-            FROM_HERE, {web::WebThread::UI},
-            base::BindOnce(&SnapshotTabHelper::UpdateSnapshotWithCallback,
-                           weak_ptr_factory_.GetWeakPtr(), /*callback=*/nil),
-            base::TimeDelta::FromSeconds(1));
-        return;
-      }
+      base::PostDelayedTaskWithTraits(
+          FROM_HERE, {web::WebThread::UI},
+          base::BindOnce(&SnapshotTabHelper::UpdateSnapshotWithCallback,
+                         weak_ptr_factory_.GetWeakPtr(), /*callback=*/nil),
+          base::TimeDelta::FromSeconds(1));
+      return;
     }
-    // Pre-iOS 11 and native content cannot utilize the WKWebView snapshotting
-    // API.
+    // Native content cannot utilize the WKWebView snapshotting API.
     UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
   }
   ignore_next_load_ = false;
diff --git a/ios/chrome/browser/tabs/tab.h b/ios/chrome/browser/tabs/tab.h
index bba48a32..c3fd137 100644
--- a/ios/chrome/browser/tabs/tab.h
+++ b/ios/chrome/browser/tabs/tab.h
@@ -108,10 +108,6 @@
 // TODO(crbug.com/228575): Create a delegate interface and remove this.
 - (void)setParentTabModel:(TabModel*)model;
 
-// The view to display in the view hierarchy based on the current URL. Won't be
-// nil. It is up to the caller to size the view and confirm |webUsageEnabled|.
-- (UIView*)view;
-
 // The view that generates print data when printing. It can be nil when printing
 // is not supported with this tab. It can be different from |Tab view|.
 - (UIView*)viewForPrinting;
diff --git a/ios/chrome/browser/tabs/tab.mm b/ios/chrome/browser/tabs/tab.mm
index b8a8cb9..abfc628 100644
--- a/ios/chrome/browser/tabs/tab.mm
+++ b/ios/chrome/browser/tabs/tab.mm
@@ -267,22 +267,6 @@
   _parentTabModel = model;
 }
 
-- (UIView*)view {
-  if (!self.webState)
-    return nil;
-
-  // Record reload of previously-evicted tab.
-  if (self.webState->IsEvicted() && [_parentTabModel tabUsageRecorder])
-    [_parentTabModel tabUsageRecorder]->RecordPageLoadStart(self.webState);
-
-  // Do not trigger the load if the tab has crashed. SadTabTabHelper is
-  // responsible for handing reload logic for crashed tabs.
-  if (!self.webState->IsCrashed()) {
-    self.webState->GetNavigationManager()->LoadIfNecessary();
-  }
-  return self.webState->GetView();
-}
-
 - (UIView*)viewForPrinting {
   return self.webController.viewForPrinting;
 }
@@ -420,8 +404,7 @@
     _openInController = [[OpenInController alloc]
         initWithURLLoaderFactory:_browserState->GetSharedURLLoaderFactory()
                    webController:self.webController];
-    // If the tab was evicted before, It should have been loaded already before
-    // starting the open-in controller.
+    // Previously evicted tabs should be reloaded before this method is called.
     DCHECK(!self.webState->IsEvicted());
     self.webState->GetNavigationManager()->LoadIfNecessary();
     _openInController.baseView = self.webState->GetView();
diff --git a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
index 2cf907b..a33c485 100644
--- a/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
+++ b/ios/chrome/browser/ui/activity_services/activity_service_controller_egtest.mm
@@ -77,11 +77,9 @@
 @implementation ActivityServiceControllerTestCase
 
 - (void)testActivityServiceControllerCantPrintUnprintablePages {
-  // TODO(crbug.com/747622): re-enable this test on iOS 11 once earl grey can
-  // interact with the share menu.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Disabled on iOS 11.");
-  }
+  // TODO(crbug.com/747622): re-enable this test on once earl grey can interact
+  // with the share menu.
+  EARL_GREY_TEST_DISABLED(@"Disabled until EG can use share menu.");
 
   // TODO(crbug.com/864597): Reenable this test.
   EARL_GREY_TEST_DISABLED(@"Test should be rewritten to use Offline Version.");
@@ -127,11 +125,9 @@
 }
 
 - (void)testOpenActivityServiceControllerAndCopy {
-  // TODO(crbug.com/747622): re-enable this test on iOS 11 once earl grey can
-  // interact with the share menu.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Disabled on iOS 11.");
-  }
+  // TODO(crbug.com/747622): re-enable this test once earl grey can interact
+  // with the share menu.
+  EARL_GREY_TEST_DISABLED(@"Disabled until EG can use share menu.");
 
   // Set up mock http server.
   std::map<GURL, std::string> responses;
diff --git a/ios/chrome/browser/ui/app_launcher/open_mail_handler_view_controller.mm b/ios/chrome/browser/ui/app_launcher/open_mail_handler_view_controller.mm
index 6ebb3db..80f6c264 100644
--- a/ios/chrome/browser/ui/app_launcher/open_mail_handler_view_controller.mm
+++ b/ios/chrome/browser/ui/app_launcher/open_mail_handler_view_controller.mm
@@ -86,16 +86,6 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
-
-  // TODO(crbug.com/765146): This is needed here because crrev/c/660257 is not
-  // intended for M62 branch but this change to mailto:// handling is. This
-  // change is redundant but can co-exist with crrev/c/660257.
-  // This will be reverted after cherrypick to M62.
-  if (@available(iOS 11, *)) {
-    self.collectionView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
-
   [self loadModel];
 }
 
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm
index 631f9b2..337e1ff 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm
@@ -68,7 +68,7 @@
   checkMarkImageView.tintColor = UIColorFromRGB(kAuthenticationCheckmarkColor);
   [option addSubview:checkMarkImageView];
 
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(option);
+  id<LayoutGuideProvider> safeArea = option.safeAreaLayoutGuide;
 
   if (text) {
     // There is text to be displayed. Make sure it is taken into account.
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
index ba783b9..442b6b4 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
@@ -51,13 +51,11 @@
   self.scrollView = [[UIScrollView alloc] init];
   self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
 
-  if (@available(iOS 11, *)) {
-    // The observed behavior was buggy. When the view appears on the screen,
-    // the scrollview was not scrolled all the way to the top. Adjusting the
-    // safe area manually fixes the issue.
-    self.scrollView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
+  // The observed behavior was buggy. When the view appears on the screen,
+  // the scrollview was not scrolled all the way to the top. Adjusting the
+  // safe area manually fixes the issue.
+  self.scrollView.contentInsetAdjustmentBehavior =
+      UIScrollViewContentInsetAdjustmentNever;
   [self.view addSubview:self.scrollView];
 
   // Scroll view container.
@@ -129,7 +127,7 @@
 
   self.options = @[ noChangeOption, reviewOption, turnOnOption ];
 
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeArea = self.view.safeAreaLayoutGuide;
   AddSameConstraints(self.view, self.scrollView);
   AddSameConstraints(container, self.scrollView);
   AddSameCenterXConstraint(container, headerImageView);
@@ -226,17 +224,9 @@
 // Updates constraints and content insets for the |scrollView| and
 // |imageBackgroundView| related to non-safe area.
 - (void)updateScrollViewAndImageBackgroundView {
-  if (@available(iOS 11, *)) {
-    self.scrollView.contentInset = self.view.safeAreaInsets;
-    self.imageBackgroundViewHeightConstraint.constant =
-        self.view.safeAreaInsets.top;
-  } else {
-    CGFloat statusBarHeight =
-        [UIApplication sharedApplication].isStatusBarHidden ? 0.
-                                                            : StatusBarHeight();
-    self.scrollView.contentInset = UIEdgeInsetsMake(statusBarHeight, 0, 0, 0);
-    self.imageBackgroundViewHeightConstraint.constant = statusBarHeight;
-  }
+  self.scrollView.contentInset = self.view.safeAreaInsets;
+  self.imageBackgroundViewHeightConstraint.constant =
+      self.view.safeAreaInsets.top;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_view_controller.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_view_controller.mm
index b87933f..f0480900 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_view_controller.mm
@@ -230,7 +230,7 @@
   self.secondaryMoreButtonMarginConstraint = [self.moreButton.leadingAnchor
       constraintGreaterThanOrEqualToAnchor:self.moreOptionsButton.trailingAnchor
                                   constant:kMargin];
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeArea = self.view.safeAreaLayoutGuide;
   AddSameConstraintsToSides(self.view, self.gradientView,
                             LayoutSides::kLeading | LayoutSides::kTrailing);
   AddSameConstraintsToSides(
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.mm
index 81f7af62..e8083f9 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.mm
@@ -55,21 +55,11 @@
   [self.identityView setAvatar:image];
   self.accessoryType = checked ? UITableViewCellAccessoryCheckmark
                                : UITableViewCellAccessoryNone;
-  if (@available(iOS 11, *)) {
-    if (checked) {
-      self.directionalLayoutMargins =
-          NSDirectionalEdgeInsetsMake(0, 0, 0, kCheckmarkMagin);
-    } else {
-      self.directionalLayoutMargins = NSDirectionalEdgeInsetsZero;
-    }
+  if (checked) {
+    self.directionalLayoutMargins =
+        NSDirectionalEdgeInsetsMake(0, 0, 0, kCheckmarkMagin);
   } else {
-    if (!checked) {
-      self.layoutMargins = UIEdgeInsetsZero;
-    } else if (base::i18n::IsRTL()) {
-      self.layoutMargins = UIEdgeInsetsMake(0, kCheckmarkMagin, 0, 0);
-    } else {
-      self.layoutMargins = UIEdgeInsetsMake(0, 0, 0, kCheckmarkMagin);
-    }
+    self.directionalLayoutMargins = NSDirectionalEdgeInsetsZero;
   }
 }
 
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_presentation_controller.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_presentation_controller.mm
index 6322515c..9fb92c8 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_presentation_controller.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_presentation_controller.mm
@@ -37,7 +37,7 @@
 
 - (CGRect)frameOfPresentedViewInContainerView {
   CGRect safeAreaFrame = UIEdgeInsetsInsetRect(
-      self.containerView.bounds, SafeAreaInsetsForView(self.containerView));
+      self.containerView.bounds, self.containerView.safeAreaInsets);
 
   CGFloat availableWidth = CGRectGetWidth(safeAreaFrame);
   CGFloat availableHeight = CGRectGetHeight(safeAreaFrame);
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
index 8d9b5a3..949de45c 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
@@ -161,13 +161,11 @@
   self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
   self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
   self.scrollView.accessibilityIdentifier = kUnifiedConsentScrollViewIdentifier;
-  if (@available(iOS 11, *)) {
-    // The observed behavior was buggy. When the view appears on the screen,
-    // the scrollview was not scrolled all the way to the top. Adjusting the
-    // safe area manually fixes the issue.
-    self.scrollView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
+  // The observed behavior was buggy. When the view appears on the screen,
+  // the scrollview was not scrolled all the way to the top. Adjusting the
+  // safe area manually fixes the issue.
+  self.scrollView.contentInsetAdjustmentBehavior =
+      UIScrollViewContentInsetAdjustmentNever;
   [self.view addSubview:self.scrollView];
 
   // Scroll view container.
@@ -299,7 +297,7 @@
       constraintEqualToAnchor:self.identityPickerView.bottomAnchor
                      constant:kVerticalTextMargin];
   // Adding constraints for the container.
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeArea = self.view.safeAreaLayoutGuide;
   [container.widthAnchor constraintEqualToAnchor:safeArea.widthAnchor].active =
       YES;
   // Adding constraints for |imageBackgroundView|.
@@ -454,17 +452,9 @@
 // Updates constraints and content insets for the |scrollView| and
 // |imageBackgroundView| related to non-safe area.
 - (void)updateScrollViewAndImageBackgroundView {
-  if (@available(iOS 11, *)) {
-    self.scrollView.contentInset = self.view.safeAreaInsets;
-    self.imageBackgroundViewHeightConstraint.constant =
-        self.view.safeAreaInsets.top;
-  } else {
-    CGFloat statusBarHeight =
-        [UIApplication sharedApplication].isStatusBarHidden ? 0.
-                                                            : StatusBarHeight();
-    self.scrollView.contentInset = UIEdgeInsetsMake(statusBarHeight, 0, 0, 0);
-    self.imageBackgroundViewHeightConstraint.constant = statusBarHeight;
-  }
+  self.scrollView.contentInset = self.view.safeAreaInsets;
+  self.imageBackgroundViewHeightConstraint.constant =
+      self.view.safeAreaInsets.top;
   if (self.scrollView.delegate == self) {
     // Don't send the notification if the delegate is not configured yet.
     [self sendDidReachBottomIfReached];
diff --git a/ios/chrome/browser/ui/autofill/BUILD.gn b/ios/chrome/browser/ui/autofill/BUILD.gn
index c9a66d9..5bc0e4a 100644
--- a/ios/chrome/browser/ui/autofill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/BUILD.gn
@@ -33,9 +33,11 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser/autofill",
     "//ios/chrome/browser/autofill:autofill_shared",
+    "//ios/chrome/browser/autofill/manual_fill",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/infobars",
     "//ios/chrome/browser/metrics",
+    "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/passwords:passwords_generation_utils",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/ssl",
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
index debfe39..1509da2 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory_coordinator.mm
@@ -7,7 +7,11 @@
 #include "base/mac/foundation_util.h"
 #include "components/autofill/core/common/autofill_features.h"
 #import "components/autofill/ios/browser/js_suggestion_manager.h"
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/password_manager/core/browser/password_store.h"
 #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
+#import "ios/chrome/browser/autofill/manual_fill/passwords_fetcher.h"
+#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
 #import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/address_coordinator.h"
 #import "ios/chrome/browser/ui/autofill/manual_fill/card_coordinator.h"
@@ -24,7 +28,8 @@
     ManualFillAccessoryViewControllerDelegate,
     AddressCoordinatorDelegate,
     CardCoordinatorDelegate,
-    PasswordCoordinatorDelegate>
+    PasswordCoordinatorDelegate,
+    PasswordFetcherDelegate>
 
 // The Mediator for the input accessory view controller.
 @property(nonatomic, strong)
@@ -43,6 +48,9 @@
 @property(nonatomic, strong)
     ManualFillInjectionHandler* manualFillInjectionHandler;
 
+// The password fetcher used to inform if passwords are available.
+@property(nonatomic, strong) PasswordFetcher* passwordFetcher;
+
 // The WebStateList for this instance. Used to instantiate the child
 // coordinators lazily.
 @property(nonatomic, assign) WebStateList* webStateList;
@@ -78,6 +86,17 @@
     _formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
         initWithConsumer:self.formInputAccessoryViewController
             webStateList:webStateList];
+
+    auto passwordStore = IOSChromePasswordStoreFactory::GetForBrowserState(
+        browserState, ServiceAccessType::EXPLICIT_ACCESS);
+    // In BVC unit tests the password store doesn't exist. Skip creating the
+    // fetcher.
+    // TODO:(crbug.com/878388) Remove this workaround.
+    if (passwordStore) {
+      _passwordFetcher =
+          [[PasswordFetcher alloc] initWithPasswordStore:passwordStore
+                                                delegate:self];
+    }
   }
   return self;
 }
@@ -195,4 +214,13 @@
   [self.delegate openAddressSettings];
 }
 
+#pragma mark - PasswordFetcherDelegate
+
+- (void)passwordFetcher:(PasswordFetcher*)passwordFetcher
+      didFetchPasswords:
+          (std::vector<std::unique_ptr<autofill::PasswordForm>>&)passwords {
+  self.manualFillAccessoryViewController.passwordButtonHidden =
+      passwords.empty();
+}
+
 @end
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
index 144da80..194bfb0 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.mm
@@ -15,16 +15,6 @@
 namespace {
 // Left and right margins of the cell contents.
 static const CGFloat sideMargins = 16;
-// The base multiplier for the top and bottom margins.  This number multiplied
-// by the font size plus the base margins will give similar results to
-// |constraintEqualToSystemSpacingBelowAnchor:|.
-static const CGFloat iOS10MarginFontMultiplier = 1.18;
-// The base top margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseTopMargin = 4;
-// The base bottom margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseBottomMargin = 4;
 // The multiplier for the base system spacing at the top margin.
 static const CGFloat TopBaseSystemSpacingMultiplier = 1.1;
 // The multiplier for the base system spacing at the bottom margin.
@@ -110,44 +100,23 @@
              forControlEvents:UIControlEventTouchUpInside];
   self.titleButton.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
   [self.contentView addSubview:self.titleButton];
-  id<LayoutGuideProvider> safeArea =
-      SafeAreaLayoutGuideForView(self.contentView);
+  id<LayoutGuideProvider> safeArea = self.contentView.safeAreaLayoutGuide;
 
   NSArray* verticalConstraints;
-  if (@available(iOS 11, *)) {
-    // Multipliers of these constraints are calculated based on a 24 base
-    // system spacing.
-    verticalConstraints = @[
-      // Vertical constraints.
-      [self.titleButton.firstBaselineAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.contentView.topAnchor
-                                         multiplier:
-                                             TopBaseSystemSpacingMultiplier],
-      [self.contentView.bottomAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.titleButton
-                                                        .lastBaselineAnchor
-                                         multiplier:
-                                             BottomBaseSystemSpacingMultiplier],
-    ];
-  } else {
-    CGFloat pointSize = self.titleButton.titleLabel.font.pointSize;
-    // These margins are based on the design size and the current point size.
-    // The multipliers were selected by manually testing the different system
-    // font sizes.
-    CGFloat marginTop =
-        iOS10BaseTopMargin + pointSize * iOS10MarginFontMultiplier;
-    CGFloat marginBottom =
-        iOS10BaseBottomMargin + pointSize * iOS10MarginFontMultiplier;
-
-    verticalConstraints = @[
-      [self.titleButton.firstBaselineAnchor
-          constraintEqualToAnchor:self.contentView.topAnchor
-                         constant:marginTop],
-      [self.contentView.bottomAnchor
-          constraintEqualToAnchor:self.titleButton.lastBaselineAnchor
-                         constant:marginBottom],
-    ];
-  }
+  // Multipliers of these constraints are calculated based on a 24 base
+  // system spacing.
+  verticalConstraints = @[
+    // Vertical constraints.
+    [self.titleButton.firstBaselineAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.contentView.topAnchor
+                                       multiplier:
+                                           TopBaseSystemSpacingMultiplier],
+    [self.contentView.bottomAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.titleButton
+                                                      .lastBaselineAnchor
+                                       multiplier:
+                                           BottomBaseSystemSpacingMultiplier],
+  ];
   [NSLayoutConstraint activateConstraints:verticalConstraints];
   // Horizontal constraints.
   [NSLayoutConstraint activateConstraints:@[
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.mm b/ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.mm
index e1a208b..72d1b4e 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.mm
@@ -103,7 +103,7 @@
   if (self.contentInsetsAlwaysEqualToSafeArea && !IsIPadIdiom()) {
     // Resets the table view content inssets to be equal to the safe area
     // insets.
-    self.tableView.contentInset = SafeAreaInsetsForView(self.view);
+    self.tableView.contentInset = self.view.safeAreaInsets;
   }
 }
 
@@ -115,7 +115,7 @@
     CGRect keyboardFrame =
         [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
     CGFloat keyboardHeight = keyboardFrame.size.height;
-    UIEdgeInsets safeInsets = SafeAreaInsetsForView(self.view);
+    UIEdgeInsets safeInsets = self.view.safeAreaInsets;
     self.tableView.contentInset =
         UIEdgeInsetsMake(safeInsets.top, safeInsets.left,
                          safeInsets.bottom - keyboardHeight, safeInsets.right);
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h
index e6025639..0dd39ff6 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h
@@ -41,6 +41,10 @@
 // shown above the keyboard on iPhone and above the manual fill view.
 @interface ManualFillAccessoryViewController : UIViewController
 
+// Changing this property hides and shows the password button.
+@property(nonatomic, assign, getter=isPasswordButtonHidden)
+    BOOL passwordButtonHidden;
+
 // Instances an object with the desired delegate.
 //
 // @param delegate The delegate for this object.
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.mm
index e0cc3b8..6632248 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.mm
@@ -41,24 +41,28 @@
 
 @interface ManualFillAccessoryViewController ()
 
+// Delegate to handle interactions.
 @property(nonatomic, readonly, weak)
     id<ManualFillAccessoryViewControllerDelegate>
         delegate;
 
+// The button to close manual fallback.
 @property(nonatomic, strong) UIButton* keyboardButton;
+
+// The button to open the passwords section.
 @property(nonatomic, strong) UIButton* passwordButton;
+
+// The button to open the credit cards section.
 @property(nonatomic, strong) UIButton* cardsButton;
+
+// The button to open the profiles section.
 @property(nonatomic, strong) UIButton* accountButton;
 
 @end
 
 @implementation ManualFillAccessoryViewController
 
-@synthesize delegate = _delegate;
-@synthesize keyboardButton = _keyboardButton;
-@synthesize passwordButton = _passwordButton;
-@synthesize cardsButton = _cardsButton;
-@synthesize accountButton = _accountButton;
+#pragma mark - Public
 
 - (instancetype)initWithDelegate:
     (id<ManualFillAccessoryViewControllerDelegate>)delegate {
@@ -69,6 +73,24 @@
   return self;
 }
 
+- (void)reset {
+  [self resetTintColors];
+  self.keyboardButton.hidden = YES;
+  self.keyboardButton.alpha = 0.0;
+}
+
+#pragma mark - Setters
+
+- (void)setPasswordButtonHidden:(BOOL)passwordButtonHidden {
+  if (passwordButtonHidden == _passwordButtonHidden) {
+    return;
+  }
+  _passwordButton.hidden = passwordButtonHidden;
+  _passwordButtonHidden = passwordButtonHidden;
+}
+
+#pragma mark - Private
+
 - (void)loadView {
   self.view = [[UIView alloc] init];
   self.view.translatesAutoresizingMaskIntoConstraints = NO;
@@ -100,6 +122,7 @@
                 forControlEvents:UIControlEventTouchUpInside];
   self.passwordButton.accessibilityIdentifier =
       manual_fill::AccessoryPasswordAccessibilityIdentifier;
+  self.passwordButton.hidden = self.isPasswordButtonHidden;
   [icons addObject:self.passwordButton];
 
   if (autofill::features::IsAutofillManualFallbackEnabled()) {
@@ -133,8 +156,7 @@
   stackView.translatesAutoresizingMaskIntoConstraints = NO;
   [self.view addSubview:stackView];
 
-  id<LayoutGuideProvider> safeAreaLayoutGuide =
-      SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeAreaLayoutGuide = self.view.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     // Vertical constraints.
     [stackView.heightAnchor constraintEqualToAnchor:self.view.heightAnchor],
@@ -152,12 +174,6 @@
   self.keyboardButton.alpha = 0.0;
 }
 
-- (void)reset {
-  [self resetTintColors];
-  self.keyboardButton.hidden = YES;
-  self.keyboardButton.alpha = 0.0;
-}
-
 // Resets the colors of all the icons to the active color.
 - (void)resetTintColors {
   UIColor* activeTintColor = [self activeTintColor];
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.mm
index dd43ea0c..24d32ff9 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.mm
@@ -453,8 +453,7 @@
   self.stateCountryLineConstraints = @[];
   self.verticalConstraints = @[];
 
-  id<LayoutGuideProvider> safeArea =
-      SafeAreaLayoutGuideForView(self.contentView);
+  id<LayoutGuideProvider> safeArea = self.contentView.safeAreaLayoutGuide;
 
   [NSLayoutConstraint activateConstraints:@[
     // Common vertical constraints.
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
index 040ccc8..66e6e68 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.mm
@@ -69,24 +69,6 @@
 // Left and right margins of the cell content.
 static const CGFloat sideMargins = 16;
 
-// The base multiplier for the top and bottom margins. This number multiplied by
-// the font size plus the base margins will give similar results to
-// |constraintEqualToSystemSpacingBelowAnchor:| which is not available on iOS
-// 10.
-static const CGFloat iOS10MarginFontMultiplier = 1.18;
-
-// The base top margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseTopMargin = 28;
-
-// The base middle margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseMiddleMargin = 24;
-
-// The base bottom margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseBottomMargin = 18;
-
 // The multiplier for the base system spacing at the top margin.
 static const CGFloat TopSystemSpacingMultiplier = 1.58;
 
@@ -268,8 +250,7 @@
   ]
                                     container:self.contentView];
 
-  id<LayoutGuideProvider> safeArea =
-      SafeAreaLayoutGuideForView(self.contentView);
+  id<LayoutGuideProvider> safeArea = self.contentView.safeAreaLayoutGuide;
 
   [NSLayoutConstraint activateConstraints:@[
     // Common vertical constraints.
@@ -336,50 +317,23 @@
 - (void)setVerticalSpacingConstraintsForViews:(NSArray<UIView*>*)views
                                     container:(UIView*)container {
   NSMutableArray* verticalConstraints = [[NSMutableArray alloc] init];
-  if (@available(iOS 11, *)) {
-    // Multipliers of these constraints are calculated based on a 24 base
-    // system spacing.
-    NSLayoutYAxisAnchor* previousAnchor = container.topAnchor;
-    CGFloat multiplier = TopSystemSpacingMultiplier;
-    for (UIView* view in views) {
-      [verticalConstraints
-          addObject:[view.firstBaselineAnchor
-                        constraintEqualToSystemSpacingBelowAnchor:previousAnchor
-                                                       multiplier:multiplier]];
-      multiplier = MiddleSystemSpacingMultiplier;
-      previousAnchor = view.lastBaselineAnchor;
-    }
-    multiplier = BottomSystemSpacingMultiplier;
+  // Multipliers of these constraints are calculated based on a 24 base
+  // system spacing.
+  NSLayoutYAxisAnchor* previousAnchor = container.topAnchor;
+  CGFloat multiplier = TopSystemSpacingMultiplier;
+  for (UIView* view in views) {
     [verticalConstraints
-        addObject:[container.bottomAnchor
+        addObject:[view.firstBaselineAnchor
                       constraintEqualToSystemSpacingBelowAnchor:previousAnchor
                                                      multiplier:multiplier]];
-  } else {
-    CGFloat pointSize = self.cardNumberButton.titleLabel.font.pointSize;
-    // These margins are based on the design size and the current point size.
-    // The multipliers were selected by manually testing the different system
-    // font sizes.
-    CGFloat marginBetweenButtons =
-        iOS10BaseMiddleMargin + pointSize * iOS10MarginFontMultiplier;
-    CGFloat marginBottom =
-        iOS10BaseBottomMargin + pointSize * iOS10MarginFontMultiplier / 2;
-    CGFloat marginTop =
-        iOS10BaseTopMargin + pointSize * iOS10MarginFontMultiplier / 2;
-
-    NSLayoutYAxisAnchor* previousAnchor = container.topAnchor;
-    CGFloat constant = marginTop;
-    for (UIView* view in views) {
-      [verticalConstraints addObject:[view.firstBaselineAnchor
-                                         constraintEqualToAnchor:previousAnchor
-                                                        constant:constant]];
-      constant = marginBetweenButtons;
-      previousAnchor = view.lastBaselineAnchor;
-    }
-    [verticalConstraints addObject:[container.bottomAnchor
-                                       constraintEqualToAnchor:previousAnchor
-                                                      constant:marginBottom]];
+    multiplier = MiddleSystemSpacingMultiplier;
+    previousAnchor = view.lastBaselineAnchor;
   }
-
+  multiplier = BottomSystemSpacingMultiplier;
+  [verticalConstraints
+      addObject:[container.bottomAnchor
+                    constraintEqualToSystemSpacingBelowAnchor:previousAnchor
+                                                   multiplier:multiplier]];
   [NSLayoutConstraint activateConstraints:verticalConstraints];
 }
 
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.mm b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.mm
index a86b551..7e0bb2fa 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.mm
@@ -50,20 +50,6 @@
 namespace {
 // Left and right margins of the cell content.
 static const CGFloat sideMargins = 16;
-// The base multiplier for the top and bottom margins. This number multiplied by
-// the font size plus the base margins will give similar results to
-// |constraintEqualToSystemSpacingBelowAnchor:| which is not available on iOS
-// 10.
-static const CGFloat iOS10MarginFontMultiplier = 1.18;
-// The base top margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseTopMargin = 28;
-// The base middle margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseMiddleMargin = 24;
-// The base bottom margin, only used in iOS 10. Refer to
-// |iOS10MarginFontMultiplier| for how it is used.
-static const CGFloat iOS10BaseBottomMargin = 18;
 // The multiplier for the base system spacing at the top margin.
 static const CGFloat TopSystemSpacingMultiplier = 1.58;
 // The multiplier for the base system spacing between elements (vertical).
@@ -190,61 +176,31 @@
                 forControlEvents:UIControlEventTouchUpInside];
   [self.contentView addSubview:self.passwordButton];
 
-  id<LayoutGuideProvider> safeArea =
-      SafeAreaLayoutGuideForView(self.contentView);
+  id<LayoutGuideProvider> safeArea = self.contentView.safeAreaLayoutGuide;
 
   NSArray* verticalConstraints;
-  if (@available(iOS 11, *)) {
-    // Multipliers of these constraints are calculated based on a 24 base
-    // system spacing.
-    verticalConstraints = @[
-      [self.siteNameLabel.firstBaselineAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.contentView.topAnchor
-                                         multiplier:TopSystemSpacingMultiplier],
-      [self.usernameButton.firstBaselineAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.siteNameLabel
-                                                        .lastBaselineAnchor
-                                         multiplier:
-                                             MiddleSystemSpacingMultiplier],
-      [self.passwordButton.firstBaselineAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.usernameButton
-                                                        .lastBaselineAnchor
-                                         multiplier:
-                                             MiddleSystemSpacingMultiplier],
-      [self.contentView.bottomAnchor
-          constraintEqualToSystemSpacingBelowAnchor:self.passwordButton
-                                                        .lastBaselineAnchor
-                                         multiplier:
-                                             BottomSystemSpacingMultiplier],
-    ];
-  } else {
-    CGFloat pointSize = self.usernameButton.titleLabel.font.pointSize;
-    // These margins are based on the design size and the current point size.
-    // The multipliers were selected by manually testing the different system
-    // font sizes.
-    CGFloat marginBetweenButtons =
-        iOS10BaseMiddleMargin + pointSize * iOS10MarginFontMultiplier;
-    CGFloat marginBottom =
-        iOS10BaseBottomMargin + pointSize * iOS10MarginFontMultiplier / 2;
-    CGFloat marginTop =
-        iOS10BaseTopMargin + pointSize * iOS10MarginFontMultiplier / 2;
-
-    verticalConstraints = @[
-      // This doesn't make sense when the label is to big.
-      [self.siteNameLabel.firstBaselineAnchor
-          constraintEqualToAnchor:self.contentView.topAnchor
-                         constant:marginTop],
-      [self.usernameButton.firstBaselineAnchor
-          constraintEqualToAnchor:self.siteNameLabel.lastBaselineAnchor
-                         constant:marginBetweenButtons],
-      [self.passwordButton.firstBaselineAnchor
-          constraintEqualToAnchor:self.usernameButton.lastBaselineAnchor
-                         constant:marginBetweenButtons],
-      [self.contentView.bottomAnchor
-          constraintEqualToAnchor:self.passwordButton.lastBaselineAnchor
-                         constant:marginBottom],
-    ];
-  }
+  // Multipliers of these constraints are calculated based on a 24 base
+  // system spacing.
+  verticalConstraints = @[
+    [self.siteNameLabel.firstBaselineAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.contentView.topAnchor
+                                       multiplier:TopSystemSpacingMultiplier],
+    [self.usernameButton.firstBaselineAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.siteNameLabel
+                                                      .lastBaselineAnchor
+                                       multiplier:
+                                           MiddleSystemSpacingMultiplier],
+    [self.passwordButton.firstBaselineAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.usernameButton
+                                                      .lastBaselineAnchor
+                                       multiplier:
+                                           MiddleSystemSpacingMultiplier],
+    [self.contentView.bottomAnchor
+        constraintEqualToSystemSpacingBelowAnchor:self.passwordButton
+                                                      .lastBaselineAnchor
+                                       multiplier:
+                                           BottomSystemSpacingMultiplier],
+  ];
 
   [NSLayoutConstraint activateConstraints:verticalConstraints];
   [NSLayoutConstraint activateConstraints:@[
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller.mm
index 1498c90..1f46d75 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller.mm
@@ -91,27 +91,13 @@
   self.definesPresentationContext = YES;
   self.searchController.searchBar.backgroundColor = [UIColor clearColor];
   self.searchController.obscuresBackgroundDuringPresentation = NO;
-  if (@available(iOS 11, *)) {
-    self.navigationItem.searchController = self.searchController;
-    self.navigationItem.hidesSearchBarWhenScrolling = NO;
-  } else {
-    self.tableView.tableHeaderView = self.searchController.searchBar;
-  }
+  self.navigationItem.searchController = self.searchController;
+  self.navigationItem.hidesSearchBarWhenScrolling = NO;
   self.searchController.searchBar.accessibilityIdentifier =
       manual_fill::PasswordSearchBarAccessibilityIdentifier;
   NSString* titleString =
       l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_USE_OTHER_PASSWORD);
   self.title = titleString;
-
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // On iOS 11 this is not needed since the cell constrains are updated by the
-    // system.
-    [[NSNotificationCenter defaultCenter]
-        addObserver:self.tableView
-           selector:@selector(reloadData)
-               name:UIContentSizeCategoryDidChangeNotification
-             object:nil];
-  }
 }
 
 #pragma mark - ManualFillPasswordConsumer
@@ -137,7 +123,7 @@
   if (self.contentInsetsAlwaysEqualToSafeArea && !IsIPadIdiom()) {
     // Resets the table view content inssets to be equal to the safe area
     // insets.
-    self.tableView.contentInset = SafeAreaInsetsForView(self.view);
+    self.tableView.contentInset = self.view.safeAreaInsets;
   }
 }
 
@@ -149,7 +135,7 @@
     CGRect keyboardFrame =
         [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
     CGFloat keyboardHeight = keyboardFrame.size.height;
-    UIEdgeInsets safeInsets = SafeAreaInsetsForView(self.view);
+    UIEdgeInsets safeInsets = self.view.safeAreaInsets;
     self.tableView.contentInset =
         UIEdgeInsetsMake(safeInsets.top, safeInsets.left,
                          safeInsets.bottom - keyboardHeight, safeInsets.right);
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index 7fa6064..025f1bf 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -249,6 +249,7 @@
   const GURL URL = self.testServer->GetURL(kFormHTMLFile);
   [ChromeEarlGrey loadURL:URL];
   [ChromeEarlGrey waitForWebViewContainingText:"hello!"];
+  SaveExamplePasswordForm();
 }
 
 - (void)tearDown {
@@ -260,10 +261,6 @@
 
 // Tests that the passwords view controller appears on screen.
 - (void)testPasswordsViewControllerIsPresented {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -280,10 +277,6 @@
 // Tests that the passwords view controller contains the "Manage Passwords..."
 // action.
 - (void)testPasswordsViewControllerContainsManagePasswordsAction {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -299,10 +292,6 @@
 
 // Tests that the "Manage Passwords..." action works.
 - (void)testManagePasswordsActionOpensPasswordSettings {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -322,10 +311,6 @@
 
 // Tests that the Password View Controller is not present when presenting UI.
 - (void)testPasswordControllerPauses {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // For the search bar to appear in password settings at least one password is
   // needed.
   SaveExamplePasswordForm();
@@ -355,10 +340,6 @@
 // Tests that the Password View Controller is resumed after selecting other
 // password.
 - (void)testPasswordControllerResumes {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // For this test one password is needed.
   SaveExamplePasswordForm();
 
@@ -401,10 +382,6 @@
 // Tests that the Password View Controller is resumed after dismissing "Other
 // Passwords".
 - (void)testPasswordControllerResumesWhenOtherPasswordsDismiss {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -439,10 +416,6 @@
 // Tests that the Password View Controller is dismissed when tapping the
 // keyboard icon.
 - (void)testKeyboardIconDismissPasswordController {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   if (IsIPadIdiom()) {
     // The keyboard icon is never present in iPads.
     return;
@@ -474,10 +447,6 @@
 // Tests that the Password View Controller is dismissed when tapping the outside
 // the popover on iPad.
 - (void)testIPadTappingOutsidePopOverDismissPasswordController {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   if (!IsIPadIdiom()) {
     return;
   }
@@ -510,10 +479,6 @@
 // Tests that the Password View Controller is dismissed when tapping the
 // keyboard.
 - (void)testTappingKeyboardDismissPasswordControllerPopOver {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   if (!IsIPadIdiom()) {
     return;
   }
@@ -543,10 +508,6 @@
 // Tests that after switching fields the content size of the table view didn't
 // grow.
 - (void)testPasswordControllerKeepsRightSize {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -574,10 +535,6 @@
 
 // Tests that the Password View Controller stays on rotation.
 - (void)testPasswordControllerSupportsRotation {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -600,10 +557,6 @@
 
 // Tests that content is injected in iframe messaging.
 - (void)testPasswordControllerSupportsIFrameMessaging {
-  // TODO(crbug.com/904885): re-enable this test.
-  if (base::FeatureList::IsEnabled(web::features::kSlimNavigationManager))
-    EARL_GREY_TEST_DISABLED(@"Test disabled on SlimNavigationManager.");
-
   // Iframe messaging is not supported on iOS < 11.3.
   if (!base::ios::IsRunningOnOrLater(11, 3, 0)) {
     EARL_GREY_TEST_SKIPPED(@"Skipped for iOS < 11.3");
@@ -645,4 +598,31 @@
   XCTAssertTrue(WaitForJavaScriptCondition(javaScriptCondition));
 }
 
+// Tests that the password icon is hidden when no passwords are available.
+- (void)testPasswordIconIsNotVisibleWhenPasswordStoreEmpty {
+  ClearPasswordStore();
+
+  // Bring up the keyboard.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
+
+  // Wait for the keyboard to appear.
+  [GREYKeyboard waitForKeyboardToAppear];
+
+  // Assert the password icon is not visible.
+  [[EarlGrey selectElementWithMatcher:PasswordIconMatcher()]
+      assertWithMatcher:grey_notVisible()];
+
+  // Store one password.
+  SaveExamplePasswordForm();
+
+  // Tap another field to trigger form activity.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
+      performAction:chrome_test_util::TapWebElement(kFormElementPassword)];
+
+  // Assert the password icon is visible now.
+  [[EarlGrey selectElementWithMatcher:PasswordIconMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/autofill/save_card_infobar_view.mm b/ios/chrome/browser/ui/autofill/save_card_infobar_view.mm
index 0b958ab1..c80d03fe 100644
--- a/ios/chrome/browser/ui/autofill/save_card_infobar_view.mm
+++ b/ios/chrome/browser/ui/autofill/save_card_infobar_view.mm
@@ -200,7 +200,7 @@
   // Calculate the safe area and current Toolbar height. Set the
   // bottomAnchorConstraint constant to this height to create the bottom
   // padding.
-  CGFloat bottomSafeAreaInset = SafeAreaInsetsForView(self).bottom;
+  CGFloat bottomSafeAreaInset = self.safeAreaInsets.bottom;
   CGFloat toolbarHeight = 0;
   UILayoutGuide* guide =
       [NamedGuide guideWithName:kSecondaryToolbarGuide view:self];
@@ -231,8 +231,7 @@
   } else {
     self.backgroundColor = [UIColor whiteColor];
   }
-  id<LayoutGuideProvider> safeAreaLayoutGuide =
-      SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> safeAreaLayoutGuide = self.safeAreaLayoutGuide;
 
   // Add the icon. The icon is fixed to the top leading corner of the infobar.
   // |iconContainerView| is used here because the AutoLayout constraints for
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
index 078588d..37fcfac 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
@@ -444,11 +444,8 @@
                  selectedFolder:self.folder];
   folderViewController.delegate = self;
   self.folderViewController = folderViewController;
-  if (@available(iOS 11, *)) {
-    self.folderViewController.navigationItem.largeTitleDisplayMode =
-        UINavigationItemLargeTitleDisplayModeNever;
-  }
-
+  self.folderViewController.navigationItem.largeTitleDisplayMode =
+      UINavigationItemLargeTitleDisplayModeNever;
   [self.navigationController pushViewController:self.folderViewController
                                        animated:YES];
 }
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
index db64447..5aeaacec 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
@@ -356,22 +356,17 @@
                      action:@selector(dismissSearchController:)
            forControlEvents:UIControlEventAllTouchEvents];
 
-  // For iOS 11 and later, place the search bar in the navigation bar.
-  // Otherwise place the search bar in the table view's header.
-  if (@available(iOS 11, *)) {
-    self.navigationItem.searchController = self.searchController;
-    self.navigationItem.hidesSearchBarWhenScrolling = NO;
+  // Place the search bar in the navigation bar.
+  self.navigationItem.searchController = self.searchController;
+  self.navigationItem.hidesSearchBarWhenScrolling = NO;
 
-    // Center search bar vertically so it looks centered in the header when
-    // searching.  The cancel button is centered / decentered on
-    // viewWillAppear and viewDidDisappear.
-    UIOffset offset =
-        UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
-    self.searchController.searchBar.searchFieldBackgroundPositionAdjustment =
-        offset;
-  } else {
-    self.tableView.tableHeaderView = self.searchController.searchBar;
-  }
+  // Center search bar vertically so it looks centered in the header when
+  // searching.  The cancel button is centered / decentered on
+  // viewWillAppear and viewDidDisappear.
+  UIOffset offset =
+      UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
+  self.searchController.searchBar.searchFieldBackgroundPositionAdjustment =
+      offset;
 
   self.searchTerm = @"";
 
@@ -398,29 +393,25 @@
     self.navigationController.toolbarHidden = YES;
   }
 
-  if (@available(iOS 11, *)) {
-    // Center search bar's cancel button vertically so it looks centered.
-    // We change the cancel button proxy styles, so we will return it to
-    // default in viewDidDisappear.
-    UIOffset offset =
-        UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
-    UIBarButtonItem* cancelButton = [UIBarButtonItem
-        appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
-    [cancelButton setTitlePositionAdjustment:offset
-                               forBarMetrics:UIBarMetricsDefault];
-  }
+  // Center search bar's cancel button vertically so it looks centered.
+  // We change the cancel button proxy styles, so we will return it to
+  // default in viewDidDisappear.
+  UIOffset offset =
+      UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
+  UIBarButtonItem* cancelButton = [UIBarButtonItem
+      appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
+  [cancelButton setTitlePositionAdjustment:offset
+                             forBarMetrics:UIBarMetricsDefault];
 }
 
 - (void)viewWillDisappear:(BOOL)animated {
   [super viewWillDisappear:animated];
 
-  if (@available(iOS 11, *)) {
-    // Restore to default origin offset for cancel button proxy style.
-    UIBarButtonItem* cancelButton = [UIBarButtonItem
-        appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
-    [cancelButton setTitlePositionAdjustment:UIOffsetZero
-                               forBarMetrics:UIBarMetricsDefault];
-  }
+  // Restore to default origin offset for cancel button proxy style.
+  UIBarButtonItem* cancelButton = [UIBarButtonItem
+      appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
+  [cancelButton setTitlePositionAdjustment:UIOffsetZero
+                             forBarMetrics:UIBarMetricsDefault];
 }
 
 - (void)viewDidLayoutSubviews {
@@ -749,17 +740,15 @@
 
     [self navigateAway];
 
-    if (@available(iOS 11, *)) {
-      // At root, since there's a large title, the search bar is lower than on
-      // whatever destination folder it is transitioning to (root is never
-      // reachable through search). To avoid a kink in the animation, the title
-      // is set to regular size, which means the search bar is at same level at
-      // beginning and end of animation. This controller will be replaced in
-      // |stack| so there's no need to care about restoring this.
-      if (_rootNode == self.bookmarks->root_node()) {
-        self.navigationItem.largeTitleDisplayMode =
-            UINavigationItemLargeTitleDisplayModeNever;
-      }
+    // At root, since there's a large title, the search bar is lower than on
+    // whatever destination folder it is transitioning to (root is never
+    // reachable through search). To avoid a kink in the animation, the title
+    // is set to regular size, which means the search bar is at same level at
+    // beginning and end of animation. This controller will be replaced in
+    // |stack| so there's no need to care about restoring this.
+    if (_rootNode == self.bookmarks->root_node()) {
+      self.navigationItem.largeTitleDisplayMode =
+          UINavigationItemLargeTitleDisplayModeNever;
     }
 
     auto completion = ^{
@@ -1027,11 +1016,9 @@
                                        (const bookmarks::BookmarkNode*)node {
   viewController.navigationItem.leftBarButtonItem.action = @selector(back);
   // Disable large titles on every VC but the root controller.
-  if (@available(iOS 11, *)) {
-    if (node != self.bookmarks->root_node()) {
-      viewController.navigationItem.largeTitleDisplayMode =
-          UINavigationItemLargeTitleDisplayModeNever;
-    }
+  if (node != self.bookmarks->root_node()) {
+    viewController.navigationItem.largeTitleDisplayMode =
+        UINavigationItemLargeTitleDisplayModeNever;
   }
 
   // Add custom title.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
index 9bfcd34..f034c9fa 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
@@ -105,15 +105,9 @@
       grey_anyOf(grey_accessibilityLabel(previousViewControllerLabel),
                  grey_accessibilityLabel(@"Back"), nil);
 
-  if (@available(iOS 11, *)) {
-    return grey_allOf(grey_kindOfClass([UIButton class]),
-                      grey_ancestor(grey_kindOfClass([UINavigationBar class])),
-                      buttonLabelMatcher, nil);
-  } else {
-    return grey_allOf(grey_accessibilityTrait(UIAccessibilityTraitButton),
-                      grey_ancestor(grey_kindOfClass([UINavigationBar class])),
-                      buttonLabelMatcher, nil);
-  }
+  return grey_allOf(grey_kindOfClass([UIButton class]),
+                    grey_ancestor(grey_kindOfClass([UINavigationBar class])),
+                    buttonLabelMatcher, nil);
 }
 
 // Matcher for the DONE button on the bookmarks UI.
@@ -1723,15 +1717,13 @@
 #pragma mark - BookmarksEntriesTestCase Tests
 
 - (void)testUndoDeleteBookmarkFromSwipe {
-  // TODO(crbug.com/851227): On Compact Width on iOS11, the
-  // bookmark cell is being deleted by grey_swipeFastInDirection.
+  // TODO(crbug.com/851227): On Compact Width, the bookmark cell is being
+  // deleted by grey_swipeFastInDirection.
   // grey_swipeFastInDirectionWithStartPoint doesn't work either and it might
   // fail on devices. Disabling this test under these conditions on the
   // meantime.
-  if (@available(iOS 11, *)) {
-    if (!IsCompactWidth()) {
-      EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
-    }
+  if (!IsCompactWidth()) {
+    EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad.");
   }
 
   [BookmarksTestCase setupStandardBookmarks];
@@ -1775,15 +1767,13 @@
   FLAKY_testSwipeToDeleteDisabledInEditMode
 #endif
 - (void)testSwipeToDeleteDisabledInEditMode {
-  // TODO(crbug.com/851227): On non Compact Width on iOS11, the
-  // bookmark cell is being deleted by grey_swipeFastInDirection.
+  // TODO(crbug.com/851227): On non Compact Width  the bookmark cell is being
+  // deleted by grey_swipeFastInDirection.
   // grey_swipeFastInDirectionWithStartPoint doesn't work either and it might
   // fail on devices. Disabling this test under these conditions on the
   // meantime.
-  if (@available(iOS 11, *)) {
-    if (!IsCompactWidth()) {
-      EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
-    }
+  if (!IsCompactWidth()) {
+    EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
   }
 
   [BookmarksTestCase setupStandardBookmarks];
@@ -2129,7 +2119,7 @@
                  @"Incognito tab count should be 1");
 
   // Close the incognito tab to go back to normal mode.
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [ChromeEarlGrey closeAllIncognitoTabs];
 
   // The following verifies the selected bookmarks are open in the same order as
   // in folder.
@@ -4392,15 +4382,13 @@
 
 // Tests that you can swipe URL items in search mode.
 - (void)testSearchUrlCanBeSwipedToDelete {
-  // TODO(crbug.com/851227): On non Compact Width on iOS11, the
-  // bookmark cell is being deleted by grey_swipeFastInDirection.
+  // TODO(crbug.com/851227): On non Compact Width, the bookmark cell is being
+  // deleted by grey_swipeFastInDirection.
   // grey_swipeFastInDirectionWithStartPoint doesn't work either and it might
   // fail on devices. Disabling this test under these conditions on the
   // meantime.
-  if (@available(iOS 11, *)) {
-    if (!IsCompactWidth()) {
-      EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
-    }
+  if (!IsCompactWidth()) {
+    EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
   }
 
   [BookmarksTestCase setupStandardBookmarks];
@@ -4422,15 +4410,13 @@
 
 // Tests that you can swipe folders in search mode.
 - (void)testSearchFolderCanBeSwipedToDelete {
-  // TODO(crbug.com/851227): On non Compact Width on iOS11, the
-  // bookmark cell is being deleted by grey_swipeFastInDirection.
+  // TODO(crbug.com/851227): On non Compact Width, the bookmark cell is being
+  // deleted by grey_swipeFastInDirection.
   // grey_swipeFastInDirectionWithStartPoint doesn't work either and it might
   // fail on devices. Disabling this test under these conditions on the
   // meantime.
-  if (@available(iOS 11, *)) {
-    if (!IsCompactWidth()) {
-      EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
-    }
+  if (!IsCompactWidth()) {
+    EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad on iOS11.");
   }
 
   [BookmarksTestCase setupStandardBookmarks];
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index f7f554f..1abad5c 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -1302,11 +1302,7 @@
 
 - (CGFloat)headerOffset {
   CGFloat headerOffset = 0;
-  if (@available(iOS 11, *)) {
-    headerOffset = self.view.safeAreaInsets.top;
-  } else {
-    headerOffset = StatusBarHeight();
-  }
+  headerOffset = self.view.safeAreaInsets.top;
   return [self canShowTabStrip] ? headerOffset : 0.0;
 }
 
@@ -1325,16 +1321,12 @@
 }
 
 - (BOOL)usesSafeInsetsForViewportAdjustments {
-  // The WKWebView viewport is only updatable via the safe area in iOS 11.
-  if (@available(iOS 11, *)) {
-    fullscreen::features::ViewportAdjustmentExperiment viewportExperiment =
-        fullscreen::features::GetActiveViewportExperiment();
-    return viewportExperiment ==
-               fullscreen::features::ViewportAdjustmentExperiment::SAFE_AREA ||
-           viewportExperiment ==
-               fullscreen::features::ViewportAdjustmentExperiment::HYBRID;
-  }
-  return NO;
+  fullscreen::features::ViewportAdjustmentExperiment viewportExperiment =
+      fullscreen::features::GetActiveViewportExperiment();
+  return viewportExperiment ==
+             fullscreen::features::ViewportAdjustmentExperiment::SAFE_AREA ||
+         viewportExperiment ==
+             fullscreen::features::ViewportAdjustmentExperiment::HYBRID;
 }
 
 - (BubblePresenter*)bubblePresenter {
@@ -1874,12 +1866,6 @@
     [self addConstraintsToPrimaryToolbar];
   }
 
-  // Normally this happens in -viewSafeAreaInsetsDidChange, but added here to
-  // support iOS10.
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    [self setUpViewLayout:NO];
-  }
-
   [self setNeedsStatusBarAppearanceUpdate];
 }
 
@@ -1896,21 +1882,6 @@
     [_toolbarUIUpdater updateState];
   }
                                completion:nil];
-
-  if (!self.view.window.keyWindow && !base::ios::IsRunningOnIOS11OrLater()) {
-    // When a UIViewController in a background window is rotated, its top layout
-    // guide's length is not updated before |-viewDidLayoutSubviews| is called.
-    // In order to correctly capture the status bar height in the toolbar frame,
-    // start observing the key window notification here so that the updated top
-    // layout guide length can be used to resize the primary toolbar. This is
-    // only necessary on iOS10, as the safe area insets used in iOS11 are
-    // correctly updated for background window rotations.
-    [[NSNotificationCenter defaultCenter]
-        addObserver:self
-           selector:@selector(updateToolbarHeightForKeyWindow)
-               name:UIWindowDidBecomeKeyNotification
-             object:self.view.window];
-  }
 }
 
 - (void)dismissViewControllerAnimated:(BOOL)flag
@@ -2226,15 +2197,7 @@
     return intrinsicHeight;
   // If the primary toolbar is topmost, subtract the height of the portion of
   // the unsafe area.
-  CGFloat unsafeHeight = 0.0;
-  if (@available(iOS 11, *)) {
-    unsafeHeight = self.view.safeAreaInsets.top;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    unsafeHeight = self.topLayoutGuide.length;
-  }
-#endif
+  CGFloat unsafeHeight = self.view.safeAreaInsets.top;
 
   // The topmost header is laid out |headerOffset| from the top of |view|, so
   // subtract that from the unsafe height.
@@ -2251,10 +2214,7 @@
   UIView* secondaryToolbar =
       self.secondaryToolbarCoordinator.viewController.view;
   // Add the safe area inset to the toolbar height.
-  CGFloat unsafeHeight = 0.0;
-  if (@available(iOS 11, *)) {
-    unsafeHeight = self.view.safeAreaInsets.bottom;
-  }
+  CGFloat unsafeHeight = self.view.safeAreaInsets.bottom;
   return secondaryToolbar.intrinsicContentSize.height + unsafeHeight;
 }
 
@@ -2457,12 +2417,7 @@
 - (void)setUpViewLayout:(BOOL)initialLayout {
   DCHECK([self isViewLoaded]);
 
-  CGFloat topInset = 0.0;
-  if (@available(iOS 11, *)) {
-    topInset = self.view.safeAreaInsets.top;
-  } else {
-    topInset = StatusBarHeight();
-  }
+  CGFloat topInset = self.view.safeAreaInsets.top;
 
   // Update the fake toolbar background height.
   CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
@@ -2609,7 +2564,8 @@
       _browserContainerCoordinator.viewController.contentViewController =
           viewController;
     } else {
-      _browserContainerCoordinator.viewController.contentView = tab.view;
+      _browserContainerCoordinator.viewController.contentView =
+          [self viewForTab:tab];
     }
   }
   [self updateToolbar];
@@ -2758,14 +2714,22 @@
 
 - (UIView*)viewForTab:(Tab*)tab {
   DCHECK(tab);
-  if (tab.webState) {
-    NewTabPageTabHelper* NTPHelper =
-        NewTabPageTabHelper::FromWebState(tab.webState);
-    if (NTPHelper && NTPHelper->IsActive()) {
-      return _ntpCoordinatorsForWebStates[tab.webState].viewController.view;
-    }
+  if (!tab.webState)
+    return nil;
+  web::WebState* webState = tab.webState;
+  NewTabPageTabHelper* NTPHelper = NewTabPageTabHelper::FromWebState(webState);
+  if (NTPHelper && NTPHelper->IsActive()) {
+    return _ntpCoordinatorsForWebStates[webState].viewController.view;
   }
-  return tab.view;
+  // TODO(crbug.com/904588): Move |RecordPageLoadStart| to TabUsageRecorder.
+  if (webState->IsEvicted() && [tab.parentTabModel tabUsageRecorder]) {
+    [tab.parentTabModel tabUsageRecorder]->RecordPageLoadStart(webState);
+  }
+  if (!webState->IsCrashed()) {
+    // Load the page if it was evicted by browsing data clearing logic.
+    webState->GetNavigationManager()->LoadIfNecessary();
+  }
+  return webState->GetView();
 }
 
 #pragma mark - Private Methods: Find Bar UI
@@ -3903,14 +3867,7 @@
       return 0;
     // Also subtract the top safe area so the view will appear as full screen.
     // TODO(crbug.com/826369) Remove this once NTP is out of native content.
-    if (@available(iOS 11, *)) {
-      return -self.view.safeAreaInsets.top;
-    }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-    else {
-      return -self.topLayoutGuide.length;
-    }
-#endif
+    return -self.view.safeAreaInsets.top;
   }
   return [self headerHeightForTab:tab];
 }
@@ -3940,11 +3897,7 @@
   // the browser container view is laid out using the full screen, it extends
   // past the status bar, so that additional overlap is added here.
   if (self.usesFullscreenContainer) {
-    if (@available(iOS 11, *)) {
-      collapsedToolbarHeight += self.view.safeAreaInsets.top;
-    } else {
-      collapsedToolbarHeight += StatusBarHeight();
-    }
+    collapsedToolbarHeight += self.view.safeAreaInsets.top;
   }
   return collapsedToolbarHeight;
 }
@@ -4120,12 +4073,10 @@
                              bottomToolbarHeight:(CGFloat)bottomToolbarHeight {
   UIViewController* containerViewController =
       _browserContainerCoordinator.viewController;
-  if (@available(iOS 11, *)) {
-    containerViewController.additionalSafeAreaInsets = UIEdgeInsetsMake(
-        topToolbarHeight - self.view.safeAreaInsets.top -
-            self.currentWebState->GetWebViewProxy().contentOffset.y,
-        0, 0, 0);
-  }
+  containerViewController.additionalSafeAreaInsets = UIEdgeInsetsMake(
+      topToolbarHeight - self.view.safeAreaInsets.top -
+          self.currentWebState->GetWebViewProxy().contentOffset.y,
+      0, 0, 0);
 }
 
 // Updates the padding of the web view proxy. This either resets the frame of
@@ -5171,10 +5122,6 @@
   return self.contentArea;
 }
 
-- (BOOL)isParentViewVisible {
-  return self.visible;
-}
-
 #pragma mark - SadTabCoordinatorDelegate
 
 - (void)sadTabCoordinatorDidStart:(SadTabCoordinator*)sadTabCoordinator {
diff --git a/ios/chrome/browser/ui/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view_controller_unittest.mm
index d4aa52a..48c4a21d 100644
--- a/ios/chrome/browser/ui/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view_controller_unittest.mm
@@ -194,10 +194,6 @@
     [[tabModel stub] setCurrentTab:[OCMArg any]];
     [[tabModel stub] closeAllTabs];
 
-    // Stub methods for Tab.
-    UIView* dummyView = [[UIView alloc] initWithFrame:CGRectZero];
-    [[[currentTab stub] andReturn:dummyView] view];
-
     web::WebState::CreateParams params(chrome_browser_state_.get());
     std::unique_ptr<web::WebState> webState = web::WebState::Create(params);
     webStateImpl_.reset(static_cast<web::WebStateImpl*>(webState.release()));
@@ -302,7 +298,7 @@
 
 TEST_F(BrowserViewControllerTest, TestTabSelected) {
   [bvc_ tabSelected:tab_ notifyToolbar:YES];
-  EXPECT_EQ([[tab_ view] superview], [bvc_ contentArea]);
+  EXPECT_EQ([tab_.webState->GetView() superview], [bvc_ contentArea]);
   EXPECT_TRUE(webStateImpl_->IsVisible());
 }
 
@@ -313,7 +309,7 @@
   id tabMock = (id)tab_;
   [tabMock onSelector:@selector(url) callBlockExpectation:block];
   [bvc_ tabSelected:tab_ notifyToolbar:YES];
-  EXPECT_EQ([[tab_ view] superview], [bvc_ contentArea]);
+  EXPECT_EQ([tab_.webState->GetView() superview], [bvc_ contentArea]);
   EXPECT_TRUE(webStateImpl_->IsVisible());
 }
 
diff --git a/ios/chrome/browser/ui/bubble/bubble_view_unittest.mm b/ios/chrome/browser/ui/bubble/bubble_view_unittest.mm
index ca62299..cd47a0bb 100644
--- a/ios/chrome/browser/ui/bubble/bubble_view_unittest.mm
+++ b/ios/chrome/browser/ui/bubble/bubble_view_unittest.mm
@@ -60,11 +60,7 @@
   CGSize bubbleSize = [bubble sizeThatFits:maxSize_];
 
   // The bubble should fit the label, which contains two lines of text.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    EXPECT_NEAR(329.0f, bubbleSize.width, 1.0f);
-  } else {
-    EXPECT_NEAR(402.0f, bubbleSize.width, 1.0f);
-  }
+  EXPECT_NEAR(329.0f, bubbleSize.width, 1.0f);
 
   EXPECT_NEAR(83.0f, bubbleSize.height, 2.0f);
 }
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index 64f4dcd..cbe6bf4 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -257,14 +257,7 @@
 
 - (CGFloat)overscrollHeaderHeight {
   CGFloat height = [self.headerController toolBarView].bounds.size.height;
-  CGFloat topInset = 0.0;
-  if (@available(iOS 11, *)) {
-    topInset = self.suggestionsViewController.view.safeAreaInsets.top;
-  } else {
-    // TODO(crbug.com/826369) Replace this when the NTP is contained by the
-    // BVC with |self.suggestionsViewController.topLayoutGuide.length|.
-    topInset = StatusBarHeight();
-  }
+  CGFloat topInset = self.suggestionsViewController.view.safeAreaInsets.top;
   return height + topInset;
 }
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
index b246bbc..0c257e5 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_egtest.mm
@@ -305,12 +305,7 @@
   if (!IsRegularXRegularSizeClass()) {
     // Test that the omnibox is still pinned to the top of the screen and
     // under the safe area.
-    CGFloat safeAreaTop = 0;
-    if (@available(iOS 11, *)) {
-      safeAreaTop = ntp_home::CollectionView().safeAreaInsets.top;
-    } else {
-      safeAreaTop = StatusBarHeight();
-    }
+    CGFloat safeAreaTop = ntp_home::CollectionView().safeAreaInsets.top;
 
     CGFloat contentOffset = ntp_home::CollectionView().contentOffset.y;
     CGFloat fakeOmniboxOrigin = ntp_home::FakeOmnibox().frame.origin.y;
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
index 834c317..7affd02 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_synchronizer.mm
@@ -22,17 +22,6 @@
 namespace {
 const CGFloat kShiftTilesDownAnimationDuration = 0.2;
 const CGFloat kShiftTilesUpAnimationDuration = 0.25;
-
-UIEdgeInsets SafeAreaInsetsForViewWithinNTP(UIView* view) {
-  UIEdgeInsets insets = SafeAreaInsetsForView(view);
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/826369) Replace this when the NTP is contained by the
-    // BVC with |self.collectionController.topLayoutGuide.length|.
-    insets = UIEdgeInsetsMake(StatusBarHeight(), 0, 0, 0);
-  }
-  return insets;
-}
-
 }  // namespace
 
 @interface ContentSuggestionsHeaderSynchronizer ()<UIGestureRecognizerDelegate>
@@ -177,7 +166,7 @@
   }
 
   if (self.shouldAnimateHeader) {
-    UIEdgeInsets insets = SafeAreaInsetsForViewWithinNTP(self.collectionView);
+    UIEdgeInsets insets = self.collectionView.safeAreaInsets;
     [self.headerController
         updateFakeOmniboxForOffset:self.collectionView.contentOffset.y
                        screenWidth:self.collectionView.frame.size.width
@@ -193,8 +182,7 @@
     // updated safeAreaInsets, but VC.collectionView does not until a layer
     // -viewDidLayoutSubviews.  Since self.collectionView and it's superview
     // should always have the same safeArea, this should be safe.
-    UIEdgeInsets insets =
-        SafeAreaInsetsForViewWithinNTP(self.collectionView.superview);
+    UIEdgeInsets insets = self.collectionView.superview.safeAreaInsets;
     [self.headerController
         updateFakeOmniboxForOffset:self.collectionView.contentOffset.y
                        screenWidth:width
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
index 53c6cfc..3089565 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
@@ -95,7 +95,7 @@
 - (void)addToolbarView:(UIView*)toolbarView {
   _toolBarView = toolbarView;
   [self addSubview:toolbarView];
-  id<LayoutGuideProvider> layoutGuide = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> layoutGuide = self.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     [toolbarView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
     [toolbarView.topAnchor constraintEqualToAnchor:layoutGuide.topAnchor],
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index 920bfc0..b39d0f2 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -229,7 +229,7 @@
       insetsView =
           [[UIApplication sharedApplication] keyWindow].rootViewController.view;
     }
-    UIEdgeInsets safeAreaInsets = SafeAreaInsetsForView(insetsView);
+    UIEdgeInsets safeAreaInsets = insetsView.safeAreaInsets;
     width = std::max<CGFloat>(
         0, width - safeAreaInsets.left - safeAreaInsets.right);
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
index 44bdaca..53864838 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_layout.mm
@@ -26,12 +26,7 @@
   // top of the screen.
   CGFloat minimumHeight = collectionViewHeight + headerHeight -
                           ntp_header::kScrolledToTopOmniboxBottomMargin;
-  CGFloat topSafeArea = 0;
-  if (@available(iOS 11, *)) {
-    topSafeArea = self.collectionView.safeAreaInsets.top;
-  } else {
-    topSafeArea = StatusBarHeight();
-  }
+  CGFloat topSafeArea = self.collectionView.safeAreaInsets.top;
   if (!IsRegularXRegularSizeClass(self.collectionView))
     minimumHeight -=
         ToolbarExpandedHeight(
@@ -102,12 +97,7 @@
     attributes.zIndex = NSIntegerMax;
 
     // Prevent the fake omnibox from scrolling up off of the screen.
-    CGFloat topSafeArea = 0;
-    if (@available(iOS 11, *)) {
-      topSafeArea = self.collectionView.safeAreaInsets.top;
-    } else {
-      topSafeArea = StatusBarHeight();
-    }
+    CGFloat topSafeArea = self.collectionView.safeAreaInsets.top;
     CGFloat minY =
         headerHeight - ntp_header::kFakeOmniboxScrolledToTopMargin -
         ToolbarExpandedHeight(
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recording.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recording.h
index cd4e9f4..6931f1a 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recording.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recording.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_METRICS_RECORDING_H_
 #define IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_METRICS_RECORDING_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 #include "ui/base/window_open_disposition.h"
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index 97ee580..48defa6b 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -208,17 +208,10 @@
   [super viewDidLoad];
 
   self.collectionView.prefetchingEnabled = NO;
-  if (@available(iOS 11, *)) {
-    // Overscroll action does not work well with content offset, so set this
-    // to never and internally offset the UI to account for safe area insets.
-    self.collectionView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    self.automaticallyAdjustsScrollViewInsets = NO;
-  }
-#endif
+  // Overscroll action does not work well with content offset, so set this
+  // to never and internally offset the UI to account for safe area insets.
+  self.collectionView.contentInsetAdjustmentBehavior =
+      UIScrollViewContentInsetAdjustmentNever;
   self.collectionView.accessibilityIdentifier =
       [[self class] collectionAccessibilityIdentifier];
   _collectionUpdater.collectionViewController = self;
@@ -644,15 +637,13 @@
   if (base::FeatureList::IsEnabled(web::features::kBrowserContainerFullscreen))
     return;
 
-  if (@available(iOS 11, *)) {
-    UIEdgeInsets missingTop = UIEdgeInsetsZero;
-    // During the new tab animation the browser container view controller
-    // actually matches the browser view controller frame, so safe area does
-    // work, so be sure to check the parent view controller offset.
-    if (self.parentViewController.view.frame.origin.y == StatusBarHeight())
-      missingTop = UIEdgeInsetsMake(StatusBarHeight(), 0, 0, 0);
-    self.additionalSafeAreaInsets = missingTop;
-  }
+  UIEdgeInsets missingTop = UIEdgeInsetsZero;
+  // During the new tab animation the browser container view controller
+  // actually matches the browser view controller frame, so safe area does
+  // work, so be sure to check the parent view controller offset.
+  if (self.parentViewController.view.frame.origin.y == StatusBarHeight())
+    missingTop = UIEdgeInsetsMake(StatusBarHeight(), 0, 0, 0);
+  self.additionalSafeAreaInsets = missingTop;
 }
 
 - (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer {
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index e39a252..b7bec4e 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -173,7 +173,7 @@
     EARL_GREY_TEST_DISABLED(@"Disabled for iPad due to device rotation bug.");
   }
   [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-  UIEdgeInsets safeArea = SafeAreaInsetsForView(CollectionView());
+  UIEdgeInsets safeArea = CollectionView().safeAreaInsets;
   CGFloat collectionWidth =
       CGRectGetWidth(UIEdgeInsetsInsetRect(CollectionView().bounds, safeArea));
   GREYAssertTrue(collectionWidth > 0, @"The collection width is nil.");
@@ -187,7 +187,7 @@
                            errorOrNil:nil];
   [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
 
-  safeArea = SafeAreaInsetsForView(CollectionView());
+  safeArea = CollectionView().safeAreaInsets;
   CGFloat collectionWidthAfterRotation =
       CGRectGetWidth(UIEdgeInsetsInsetRect(CollectionView().bounds, safeArea));
   GREYAssertNotEqual(collectionWidth, collectionWidthAfterRotation,
@@ -208,7 +208,7 @@
     EARL_GREY_TEST_DISABLED(@"Disabled for iPad due to device rotation bug.");
   }
   [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-  UIEdgeInsets safeArea = SafeAreaInsetsForView(CollectionView());
+  UIEdgeInsets safeArea = CollectionView().safeAreaInsets;
   CGFloat collectionWidth =
       CGRectGetWidth(UIEdgeInsetsInsetRect(CollectionView().bounds, safeArea));
   GREYAssertTrue(collectionWidth > 0, @"The collection width is nil.");
@@ -227,7 +227,7 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::SettingsDoneButton()]
       performAction:grey_tap()];
 
-  safeArea = SafeAreaInsetsForView(CollectionView());
+  safeArea = CollectionView().safeAreaInsets;
   CGFloat collectionWidthAfterRotation =
       CGRectGetWidth(UIEdgeInsetsInsetRect(CollectionView().bounds, safeArea));
   GREYAssertNotEqual(collectionWidth, collectionWidthAfterRotation,
@@ -361,11 +361,7 @@
   CGPoint previousPosition = omnibox.bounds.origin;
 
   // Tap the omnibox to focus it.
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
-                                          FakeOmniboxAccessibilityID())]
-      performAction:grey_tap()];
-  [ChromeEarlGrey
-      waitForElementWithMatcherSufficientlyVisible:chrome_test_util::Omnibox()];
+  [self focusFakebox];
 
   // Navigate and come back.
   [[EarlGrey selectElementWithMatcher:
@@ -383,10 +379,10 @@
 
 // Tests that tapping the fake omnibox focuses the real omnibox.
 - (void)testTapFakeOmnibox {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (IsRegularXRegularSizeClass() && base::ios::IsRunningOnIOS11OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsRegularXRegularSizeClass()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
   // Setup the server.
   self.testServer->RegisterRequestHandler(base::Bind(&StandardResponse));
@@ -396,11 +392,8 @@
   NSString* URL = base::SysUTF8ToNSString(pageURL.spec());
   // Tap the fake omnibox, type the URL in the real omnibox and navigate to the
   // page.
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
-                                          FakeOmniboxAccessibilityID())]
-      performAction:grey_tap()];
-  [ChromeEarlGrey
-      waitForElementWithMatcherSufficientlyVisible:chrome_test_util::Omnibox()];
+  [self focusFakebox];
+
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
       performAction:grey_typeText([URL stringByAppendingString:@"\n"])];
 
@@ -458,11 +451,7 @@
   CGPoint origin = collectionView.contentOffset;
 
   // Tap the omnibox to focus it.
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
-                                          FakeOmniboxAccessibilityID())]
-      performAction:grey_tap()];
-  [ChromeEarlGrey
-      waitForElementWithMatcherSufficientlyVisible:chrome_test_util::Omnibox()];
+  [self focusFakebox];
 
   // Offset after the fake omnibox has been tapped.
   CGPoint offsetAfterTap = collectionView.contentOffset;
@@ -471,16 +460,8 @@
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                           FakeOmniboxAccessibilityID())]
       assertWithMatcher:grey_not(grey_sufficientlyVisible())];
-  // TODO(crbug.com/826369) This should use collectionView.safeAreaInsets.top
-  // instead of -StatusBarHeight once iOS10 is dropped and the NTP is out of
-  // native content.
-  CGFloat top = 0;
-  if (@available(iOS 11, *)) {
-    top = ntp_home::CollectionView().safeAreaInsets.top;
-  } else {
-    top = StatusBarHeight();
-  }
 
+  CGFloat top = ntp_home::CollectionView().safeAreaInsets.top;
   GREYAssertTrue(offsetAfterTap.y >= origin.y + headerHeight - (60 + top),
                  @"The collection has not moved.");
 
@@ -517,11 +498,7 @@
   CGPoint origin = collectionView.contentOffset;
 
   // Tap the omnibox to focus it.
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
-                                          FakeOmniboxAccessibilityID())]
-      performAction:grey_tap()];
-  [ChromeEarlGrey
-      waitForElementWithMatcherSufficientlyVisible:chrome_test_util::Omnibox()];
+  [self focusFakebox];
 
   // Unfocus the omnibox.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::
@@ -580,4 +557,13 @@
   [ChromeEarlGrey openNewTab];
 }
 
+// Taps the fake omnibox and waits for the real omnibox to be visible.
+- (void)focusFakebox {
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          FakeOmniboxAccessibilityID())]
+      performAction:grey_tap()];
+  [ChromeEarlGrey
+      waitForElementWithMatcherSufficientlyVisible:chrome_test_util::Omnibox()];
+}
+
 @end
diff --git a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
index 0fbbe48..8987b1bd 100644
--- a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
+++ b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
@@ -440,10 +440,10 @@
 // Tests that a prompt dialog is shown, and that the completion block is called
 // with the correct value when the OK buton is tapped.
 - (void)testShowJavaScriptPromptOK {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   // Load the blank test page and show a prompt dialog.
@@ -462,10 +462,10 @@
 // Tests that a prompt dialog is shown, and that the completion block is called
 // with the correct value when the Cancel buton is tapped.
 - (void)testShowJavaScriptPromptCancelled {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   // Load the blank test page and show a prompt dialog.
@@ -544,11 +544,9 @@
 
 // Tests that an alert is presented after displaying the share menu.
 - (void)testShowJavaScriptAfterShareMenu {
-  // TODO(crbug.com/747622): re-enable this test on iOS 11 once earl grey can
-  // interact with the share menu.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Disabled on iOS 11.");
-  }
+  // TODO(crbug.com/747622): re-enable this test once earl grey can interact
+  // with the share menu.
+  EARL_GREY_TEST_DISABLED(@"Disabled until EG can use share menu.");
 
   // Load the blank test page.
   [self loadBlankTestPage];
diff --git a/ios/chrome/browser/ui/download/download_manager_egtest.mm b/ios/chrome/browser/ui/download/download_manager_egtest.mm
index b31a414..043ee43 100644
--- a/ios/chrome/browser/ui/download/download_manager_egtest.mm
+++ b/ios/chrome/browser/ui/download/download_manager_egtest.mm
@@ -169,9 +169,7 @@
   [ChromeEarlGrey loadURL:GURL(kChromeUITermsURL)];
   const char kTermsText[] = "Google Chrome Terms of Service";
   [ChromeEarlGrey waitForWebViewContainingText:kTermsText];
-  chrome_test_util::CloseCurrentTab();
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
-
+  [ChromeEarlGrey closeCurrentTab];
   GREYAssert(WaitForOpenInButton(), @"Open in... button did not show up");
 }
 
diff --git a/ios/chrome/browser/ui/find_bar/find_bar_view.mm b/ios/chrome/browser/ui/find_bar/find_bar_view.mm
index 22cf4ee..28a8c82 100644
--- a/ios/chrome/browser/ui/find_bar/find_bar_view.mm
+++ b/ios/chrome/browser/ui/find_bar/find_bar_view.mm
@@ -91,7 +91,7 @@
 // The subviews layout is the following:
 // |-[inputField]-[previousButton][nextButton]-[closeButton]-|
 - (void)setupConstraints {
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> safeArea = self.safeAreaLayoutGuide;
 
   [self.closeButton
       setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh + 1
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
index 51d3b34..bd7abab 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_egtest.mm
@@ -115,12 +115,8 @@
   if (base::FeatureList::IsEnabled(
           web::features::kBrowserContainerFullscreen) &&
       base::FeatureList::IsEnabled(web::features::kOutOfWebFullscreen)) {
-    if (@available(iOS 11, *)) {
-      yOffset -=
-          chrome_test_util::GetCurrentWebState()->GetView().safeAreaInsets.top;
-    } else {
-      yOffset -= StatusBarHeight();
-    }
+    yOffset -=
+        chrome_test_util::GetCurrentWebState()->GetView().safeAreaInsets.top;
   }
   DCHECK_LT(yOffset, 0);
   [[EarlGrey
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
index c1061f7..e84faf6 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_system_notification_observer.mm
@@ -50,21 +50,12 @@
     // Register for VoiceOVer status change notifications.  The notification
     // name has been updated in iOS 11.
     NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
-    if (@available(iOS 11, *)) {
-      [defaultCenter
-          addObserver:self
-             selector:@selector(voiceOverStatusChanged)
-                 name:UIAccessibilityVoiceOverStatusDidChangeNotification
-               object:nil];
-    }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-    else {
-      [defaultCenter addObserver:self
-                        selector:@selector(voiceOverStatusChanged)
-                            name:UIAccessibilityVoiceOverStatusChanged
-                          object:nil];
-    }
-#endif
+    [defaultCenter
+        addObserver:self
+           selector:@selector(voiceOverStatusChanged)
+               name:UIAccessibilityVoiceOverStatusDidChangeNotification
+             object:nil];
+
     // Create a disabler if VoiceOver is enabled.
     if (UIAccessibilityIsVoiceOverRunning()) {
       _voiceOverDisabler =
diff --git a/ios/chrome/browser/ui/history/history_table_view_controller.mm b/ios/chrome/browser/ui/history/history_table_view_controller.mm
index efcbe37..d1db90c 100644
--- a/ios/chrome/browser/ui/history/history_table_view_controller.mm
+++ b/ios/chrome/browser/ui/history/history_table_view_controller.mm
@@ -152,29 +152,25 @@
 - (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
 
-  if (@available(iOS 11, *)) {
-    // Center search bar's cancel button vertically so it looks centered.
-    // We change the cancel button proxy styles, so we will return it to
-    // default in viewDidDisappear.
-    UIOffset offset =
-        UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
-    UIBarButtonItem* cancelButton = [UIBarButtonItem
-        appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
-    [cancelButton setTitlePositionAdjustment:offset
-                               forBarMetrics:UIBarMetricsDefault];
-  }
+  // Center search bar's cancel button vertically so it looks centered.
+  // We change the cancel button proxy styles, so we will return it to
+  // default in viewDidDisappear.
+  UIOffset offset =
+      UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
+  UIBarButtonItem* cancelButton = [UIBarButtonItem
+      appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
+  [cancelButton setTitlePositionAdjustment:offset
+                             forBarMetrics:UIBarMetricsDefault];
 }
 
 - (void)viewWillDisappear:(BOOL)animated {
   [super viewWillDisappear:animated];
 
-  if (@available(iOS 11, *)) {
-    // Restore to default origin offset for cancel button proxy style.
-    UIBarButtonItem* cancelButton = [UIBarButtonItem
-        appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
-    [cancelButton setTitlePositionAdjustment:UIOffsetZero
-                               forBarMetrics:UIBarMetricsDefault];
-  }
+  // Restore to default origin offset for cancel button proxy style.
+  UIBarButtonItem* cancelButton = [UIBarButtonItem
+      appearanceWhenContainedInInstancesOfClasses:@[ [UISearchBar class] ]];
+  [cancelButton setTitlePositionAdjustment:UIOffsetZero
+                             forBarMetrics:UIBarMetricsDefault];
 }
 
 - (void)viewDidLoad {
@@ -241,21 +237,16 @@
                      action:@selector(dismissSearchController:)
            forControlEvents:UIControlEventAllTouchEvents];
 
-  // For iOS 11 and later, place the search bar in the navigation bar. Otherwise
-  // place the search bar in the table view's header.
-  if (@available(iOS 11, *)) {
-    self.navigationItem.searchController = self.searchController;
-    self.navigationItem.hidesSearchBarWhenScrolling = NO;
+  // Place the search bar in the navigation bar.
+  self.navigationItem.searchController = self.searchController;
+  self.navigationItem.hidesSearchBarWhenScrolling = NO;
 
-    // Center search bar and cancel button vertically so it looks centered
-    // in the header when searching.
-    UIOffset offset =
-        UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
-    self.searchController.searchBar.searchFieldBackgroundPositionAdjustment =
-        offset;
-  } else {
-    self.tableView.tableHeaderView = self.searchController.searchBar;
-  }
+  // Center search bar and cancel button vertically so it looks centered
+  // in the header when searching.
+  UIOffset offset =
+      UIOffsetMake(0.0f, kTableViewNavigationVerticalOffsetForSearchHeader);
+  self.searchController.searchBar.searchFieldBackgroundPositionAdjustment =
+      offset;
 }
 
 #pragma mark - TableViewModel
@@ -534,25 +525,15 @@
   }
   self.historyService->RemoveVisits(entries);
 
-  // Delete items from |self.tableView|.
-  // If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
-  if (@available(iOS 11, *)) {
-    [self.tableView performBatchUpdates:^{
-      [self deleteItemsFromTableViewModelWithIndex:toDeleteIndexPaths
-                          deleteItemsFromTableView:YES];
-    }
-        completion:^(BOOL) {
-          [self updateTableViewAfterDeletingEntries];
-          [self configureViewsForNonEditModeWithAnimation:YES];
-        }];
-  } else {
-    [self.tableView beginUpdates];
+  // Delete items from |self.tableView| using performBatchUpdates.
+  [self.tableView performBatchUpdates:^{
     [self deleteItemsFromTableViewModelWithIndex:toDeleteIndexPaths
                         deleteItemsFromTableView:YES];
-    [self updateTableViewAfterDeletingEntries];
-    [self configureViewsForNonEditModeWithAnimation:YES];
-    [self.tableView endUpdates];
   }
+      completion:^(BOOL) {
+        [self updateTableViewAfterDeletingEntries];
+        [self configureViewsForNonEditModeWithAnimation:YES];
+      }];
   base::RecordAction(base::UserMetricsAction("HistoryPage_RemoveSelected"));
 }
 
@@ -815,14 +796,7 @@
 
   // If there's any tableUpdates, run them.
   if (tableUpdates) {
-    // If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
-    if (@available(iOS 11, *)) {
-      [self.tableView performBatchUpdates:tableUpdates completion:nil];
-    } else {
-      [self.tableView beginUpdates];
-      tableUpdates();
-      [self.tableView endUpdates];
-    }
+    [self.tableView performBatchUpdates:tableUpdates completion:nil];
   }
   self.currentStatusMessage = newStatusMessage;
 }
diff --git a/ios/chrome/browser/ui/history/history_ui_egtest.mm b/ios/chrome/browser/ui/history/history_ui_egtest.mm
index 40f8e8a..0198e25 100644
--- a/ios/chrome/browser/ui/history/history_ui_egtest.mm
+++ b/ios/chrome/browser/ui/history/history_ui_egtest.mm
@@ -218,10 +218,10 @@
 
 // Tests that searching history displays only entries matching the search term.
 - (void)testSearchHistory {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works on iOS 11.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   [self loadTestURLs];
diff --git a/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm b/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
index 94a7dd0..d8c6d167 100644
--- a/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
+++ b/ios/chrome/browser/ui/infobars/confirm_infobar_view.mm
@@ -388,7 +388,7 @@
     leftMargin += metrics_->horizontal_space_between_icon_and_text;
   } else {
     leftMargin += metrics_->left_margin_on_first_line_when_icon_absent;
-    leftMargin += SafeAreaInsetsForView(self).left;
+    leftMargin += self.safeAreaInsets.left;
   }
   return leftMargin;
 }
@@ -396,8 +396,7 @@
 // Returns the width reserved for the close button.
 - (CGFloat)rightMarginOnFirstLine {
   return [closeButton_ imageView].image.size.width +
-         metrics_->close_button_inner_padding * 2 +
-         SafeAreaInsetsForView(self).right;
+         metrics_->close_button_inner_padding * 2 + self.safeAreaInsets.right;
 }
 
 // Returns the horizontal space available between the icon and the close
@@ -533,7 +532,7 @@
             [self layoutWideButtonAlignRight:button1_
                                    rightEdge:CGRectGetWidth(self.bounds) -
                                              metrics_->button_margin -
-                                             SafeAreaInsetsForView(self).right
+                                             self.safeAreaInsets.right
                                            y:heightOfFirstLine];
         [self layoutWideButtonAlignRight:button2_
                                rightEdge:leftOfRightmostButton -
@@ -562,7 +561,7 @@
       [self layoutWideButtonAlignRight:button
                              rightEdge:CGRectGetWidth(self.bounds) -
                                        metrics_->button_margin -
-                                       SafeAreaInsetsForView(self).right
+                                       self.safeAreaInsets.right
                                      y:heightOfFirstLine];
     }
     return metrics_->button_height;
@@ -734,7 +733,7 @@
   // The top safe area is ignored because at rest (i.e. not during animations)
   // the infobar is aligned to the bottom of the screen, and thus should not
   // have its top intersect with any safe area.
-  CGFloat bottomSafeAreaInset = SafeAreaInsetsForView(self).bottom;
+  CGFloat bottomSafeAreaInset = self.safeAreaInsets.bottom;
   requiredHeight += bottomSafeAreaInset;
 
   UILayoutGuide* guide =
@@ -1013,7 +1012,7 @@
   closeButtonSize.width += metrics_->close_button_inner_padding * 2;
   closeButtonSize.height += metrics_->close_button_inner_padding * 2;
   CGFloat x = CGRectGetMaxX(self.frame) - closeButtonSize.width -
-              SafeAreaInsetsForView(self).right;
+              self.safeAreaInsets.right;
   // Aligns the close button at the top (height includes touch padding).
   CGFloat y = 0;
   if (singleLineMode) {
@@ -1027,8 +1026,7 @@
 - (CGRect)frameOfIcon {
   CGSize iconSize = [imageView_ image].size;
   CGFloat y = metrics_->buttons_margin_top;
-  CGFloat x =
-      metrics_->close_button_margin_left + SafeAreaInsetsForView(self).left;
+  CGFloat x = metrics_->close_button_margin_left + self.safeAreaInsets.left;
   return CGRectMake(AlignValueToPixel(x), AlignValueToPixel(y), iconSize.width,
                     iconSize.height);
 }
diff --git a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
index 70342d3..541728f 100644
--- a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
@@ -17,6 +17,14 @@
 const CGFloat kAlphaChangeAnimationDuration = 0.35;
 }  // namespace
 
+@interface InfobarContainerViewController ()
+
+// Whether the controller's view is currently available.
+// YES from viewDidAppear to viewDidDisappear.
+@property(nonatomic, assign, getter=isVisible) BOOL visible;
+
+@end
+
 @implementation InfobarContainerViewController
 
 // Whenever the container or contained views are re-drawn update the layout to
@@ -26,6 +34,16 @@
   [self updateLayoutAnimated:YES];
 }
 
+- (void)viewDidAppear:(BOOL)animated {
+  [super viewDidAppear:animated];
+  self.visible = YES;
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+  self.visible = NO;
+  [super viewDidDisappear:animated];
+}
+
 #pragma mark - InfobarConsumer
 
 - (void)addInfoBarView:(UIView*)infoBarView position:(NSInteger)position {
@@ -53,9 +71,8 @@
       CGRectGetMaxY([self.positioner parentView].frame) - height;
   containerFrame.size.height = height;
 
-  BOOL isViewVisible = [self.positioner isParentViewVisible];
   auto completion = ^(BOOL finished) {
-    if (!isViewVisible)
+    if (!self.visible)
       return;
     UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
                                     self.view);
diff --git a/ios/chrome/browser/ui/infobars/infobar_egtest.mm b/ios/chrome/browser/ui/infobars/infobar_egtest.mm
index 0227d2e7..f8ef228 100644
--- a/ios/chrome/browser/ui/infobars/infobar_egtest.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_egtest.mm
@@ -154,7 +154,7 @@
 
   // Close the first tab.  Verify that there is only one tab remaining and its
   // infobar is visible.
-  chrome_test_util::CloseCurrentTab();
+  [ChromeEarlGrey closeCurrentTab];
   [ChromeEarlGrey waitForMainTabCount:1];
   VerifyTestInfoBarVisibleForCurrentTab(true, infoBarMessage);
   VerifyNumberOfInfobarsInManager(1);
diff --git a/ios/chrome/browser/ui/infobars/infobar_positioner.h b/ios/chrome/browser/ui/infobars/infobar_positioner.h
index 5133d84..bc5e0ac 100644
--- a/ios/chrome/browser/ui/infobars/infobar_positioner.h
+++ b/ios/chrome/browser/ui/infobars/infobar_positioner.h
@@ -14,9 +14,6 @@
 // View to which the popup view should be added as subview.
 - (UIView*)parentView;
 
-// YES if |parentView| is currently visible.
-- (BOOL)isParentViewVisible;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_INFOBARS_INFOBAR_POSITIONER_H_
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index 24da136..14991e5 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -5,6 +5,8 @@
 source_set("main") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "browser_coordinator.h",
+    "browser_coordinator.mm",
     "browser_view_information.h",
     "browser_view_wrangler.h",
     "browser_view_wrangler.mm",
@@ -25,6 +27,7 @@
     "//ios/chrome/browser/sessions:serialisation",
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/tabs:tabs_internal",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/public/provider/chrome/browser",
   ]
   public_deps = [
diff --git a/ios/chrome/browser/ui/main/browser_coordinator.h b/ios/chrome/browser/ui/main/browser_coordinator.h
new file mode 100644
index 0000000..91df937
--- /dev/null
+++ b/ios/chrome/browser/ui/main/browser_coordinator.h
@@ -0,0 +1,28 @@
+// 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 IOS_CHROME_BROWSER_UI_MAIN_BROWSER_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_MAIN_BROWSER_COORDINATOR_H_
+
+#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
+
+@protocol ApplicationCommands;
+@class BrowserViewController;
+@class TabModel;
+
+// Coordinator for BrowserViewController.
+@interface BrowserCoordinator : ChromeCoordinator
+
+// The main view controller.
+@property(nonatomic, strong, readonly) BrowserViewController* viewController;
+
+// Command handler for ApplicationCommands.
+@property(nonatomic, weak) id<ApplicationCommands> applicationCommandHandler;
+
+// The model.
+@property(nonatomic, weak) TabModel* tabModel;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_MAIN_BROWSER_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/main/browser_coordinator.mm b/ios/chrome/browser/ui/main/browser_coordinator.mm
new file mode 100644
index 0000000..0db3718
--- /dev/null
+++ b/ios/chrome/browser/ui/main/browser_coordinator.mm
@@ -0,0 +1,50 @@
+// 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.
+
+#import "ios/chrome/browser/ui/main/browser_coordinator.h"
+
+#import "ios/chrome/browser/ui/browser_view_controller.h"
+#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation BrowserCoordinator
+@synthesize viewController = _viewController;
+@synthesize applicationCommandHandler = _applicationCommandHandler;
+@synthesize tabModel = _tabModel;
+
+#pragma mark - ChromeCoordinator
+
+- (void)start {
+  DCHECK(self.browserState);
+  DCHECK(!self.viewController);
+  _viewController = [self createViewController];
+  [super start];
+}
+
+- (void)stop {
+  [_viewController browserStateDestroyed];
+  [_viewController shutdown];
+  _viewController = nil;
+  [super stop];
+}
+
+#pragma mark - Private
+
+// Instantiate a BrowserViewController.
+- (BrowserViewController*)createViewController {
+  BrowserViewControllerDependencyFactory* factory =
+      [[BrowserViewControllerDependencyFactory alloc]
+          initWithBrowserState:self.browserState
+                  webStateList:[self.tabModel webStateList]];
+  return [[BrowserViewController alloc]
+                initWithTabModel:self.tabModel
+                    browserState:self.browserState
+               dependencyFactory:factory
+      applicationCommandEndpoint:self.applicationCommandHandler];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/main/browser_view_information.h b/ios/chrome/browser/ui/main/browser_view_information.h
index b8b295d..a784a59 100644
--- a/ios/chrome/browser/ui/main/browser_view_information.h
+++ b/ios/chrome/browser/ui/main/browser_view_information.h
@@ -18,16 +18,16 @@
 @protocol BrowserViewInformation<NSObject>
 
 // The normal (non-OTR) BrowserViewController
-@property(nonatomic, retain) BrowserViewController* mainBVC;
+@property(nonatomic, readonly) BrowserViewController* mainBVC;
 // The normal (non-OTR) TabModel corresponding to mainBVC.
 @property(nonatomic, retain) TabModel* mainTabModel;
 // The OTR BrowserViewController.
-@property(nonatomic, retain) BrowserViewController* otrBVC;
+@property(nonatomic, readonly) BrowserViewController* otrBVC;
 // The OTR TabModel corresponding to otrBVC.
 @property(nonatomic, retain) TabModel* otrTabModel;
 // The BrowserViewController that is currently being used (one of mainBVC or
 // otrBVC). The other, if present, is in suspended mode.
-@property(nonatomic, assign) BrowserViewController* currentBVC;
+@property(nonatomic, weak) BrowserViewController* currentBVC;
 
 // Halts all tabs from all TabModels.
 - (void)haltAllTabs;
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 462dab5..85bb055 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -18,6 +18,7 @@
 #import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/ui/browser_view_controller.h"
 #import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/main/browser_coordinator.h"
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
 #import "ios/web/public/web_state/web_state.h"
 
@@ -32,6 +33,12 @@
   BOOL _isShutdown;
 }
 
+// Coordinator for non-incognito BVC.
+@property(nonatomic, strong) BrowserCoordinator* mainCoordinator;
+
+// Coordinator for incognito BVC.
+@property(nonatomic, strong) BrowserCoordinator* incognitoCoordinator;
+
 // Responsible for maintaining all state related to sharing to other devices.
 // Redeclared readwrite from the readonly declaration in the Testing interface.
 @property(nonatomic, strong, readwrite)
@@ -48,22 +55,22 @@
 // result.
 - (TabModel*)buildOtrTabModel:(BOOL)empty;
 
-// Creates the correct BrowserViewController for the corresponding browser state
+// Creates the correct BrowserCoordinator for the corresponding browser state
 // and tab model.
-- (BrowserViewController*)bvcForBrowserState:
-                              (ios::ChromeBrowserState*)browserState
-                                    tabModel:(TabModel*)tabModel;
+- (BrowserCoordinator*)coordinatorForBrowserState:
+                           (ios::ChromeBrowserState*)browserState
+                                         tabModel:(TabModel*)tabModel;
 @end
 
 @implementation BrowserViewWrangler
 
 // Properties defined in the BrowserViewInformation protocol.
-@synthesize mainBVC = _mainBVC;
 @synthesize mainTabModel = _mainTabModel;
-@synthesize otrBVC = _otrBVC;
 @synthesize otrTabModel = _otrTabModel;
 @synthesize currentBVC = _currentBVC;
 // Private properies.
+@synthesize mainCoordinator = _mainCoordinator;
+@synthesize incognitoCoordinator = _incognitoCoordinator;
 @synthesize deviceSharingManager = _deviceSharingManager;
 
 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
@@ -85,27 +92,16 @@
 #pragma mark - BrowserViewInformation property implementations
 
 - (BrowserViewController*)mainBVC {
-  if (!_mainBVC) {
+  if (!self.mainCoordinator.viewController) {
     // |_browserState| should always be set before trying to create
-    // |_mainBVC|.
+    // |mainBVC|.
     DCHECK(_browserState);
-    self.mainBVC =
-        [self bvcForBrowserState:_browserState tabModel:self.mainTabModel];
-    DCHECK(_mainBVC);
+    self.mainCoordinator = [self coordinatorForBrowserState:_browserState
+                                                   tabModel:self.mainTabModel];
+    [self.mainCoordinator start];
+    DCHECK(self.mainCoordinator.viewController);
   }
-  return _mainBVC;
-}
-
-- (void)setMainBVC:(BrowserViewController*)mainBVC {
-  if (_mainBVC == mainBVC)
-    return;
-
-  if (_mainBVC) {
-    [_mainBVC browserStateDestroyed];
-    [_mainBVC shutdown];
-  }
-
-  _mainBVC = mainBVC;
+  return self.mainCoordinator.viewController;
 }
 
 - (TabModel*)mainTabModel {
@@ -137,30 +133,20 @@
 }
 
 - (BrowserViewController*)otrBVC {
-  if (!_otrBVC) {
+  if (!self.incognitoCoordinator.viewController) {
     // |_browserState| should always be set before trying to create
-    // |_otrBVC|.
+    // |otrBVC|.
     DCHECK(_browserState);
     ios::ChromeBrowserState* otrBrowserState =
         _browserState->GetOffTheRecordChromeBrowserState();
     DCHECK(otrBrowserState);
-    self.otrBVC =
-        [self bvcForBrowserState:otrBrowserState tabModel:self.otrTabModel];
-    DCHECK(_otrBVC);
+    self.incognitoCoordinator =
+        [self coordinatorForBrowserState:otrBrowserState
+                                tabModel:self.otrTabModel];
+    [self.incognitoCoordinator start];
+    DCHECK(self.incognitoCoordinator.viewController);
   }
-  return _otrBVC;
-}
-
-- (void)setOtrBVC:(BrowserViewController*)otrBVC {
-  if (_otrBVC == otrBVC)
-    return;
-
-  if (_otrBVC) {
-    [_otrBVC browserStateDestroyed];
-    [_otrBVC shutdown];
-  }
-
-  _otrBVC = otrBVC;
+  return self.incognitoCoordinator.viewController;
 }
 
 - (TabModel*)otrTabModel {
@@ -190,7 +176,7 @@
       storageSwitcher:(id<BrowserStateStorageSwitching>)storageSwitcher {
   DCHECK(bvc != nil);
   // |bvc| should be one of the BrowserViewControllers this class already owns.
-  DCHECK(_mainBVC == bvc || _otrBVC == bvc);
+  DCHECK(self.mainBVC == bvc || self.otrBVC == bvc);
   if (self.currentBVC == bvc) {
     return;
   }
@@ -271,11 +257,13 @@
   breakpad::StopMonitoringTabStateForTabModel(self.otrTabModel);
 
   // At this stage, a new OTR BVC shouldn't be lazily constructed by calling the
-  // .otrBVC property getter. Instead, the ivar is accessed directly through the
-  // following code.
-  BOOL otrBVCIsCurrent = self.currentBVC == _otrBVC;
+  // .otrBVC property getter.
+  BOOL otrBVCIsCurrent =
+      self.currentBVC == self.incognitoCoordinator.viewController;
   @autoreleasepool {
-    self.otrBVC = nil;
+    [self.incognitoCoordinator stop];
+    self.incognitoCoordinator = nil;
+
     // There's no guarantee the tab model was ever added to the BVC (or even
     // that the BVC was created), so ensure the tab model gets notified.
     self.otrTabModel = nil;
@@ -327,8 +315,10 @@
   [_mainTabModel browserStateDestroyed];
   [_otrTabModel browserStateDestroyed];
 
-  self.mainBVC = nil;
-  self.otrBVC = nil;
+  [self.mainCoordinator stop];
+  self.mainCoordinator = nil;
+  [self.incognitoCoordinator stop];
+  self.incognitoCoordinator = nil;
 
   _browserState = nullptr;
 }
@@ -374,18 +364,15 @@
   return tabModel;
 }
 
-- (BrowserViewController*)bvcForBrowserState:
-                              (ios::ChromeBrowserState*)browserState
-                                    tabModel:(TabModel*)tabModel {
-  BrowserViewControllerDependencyFactory* factory =
-      [[BrowserViewControllerDependencyFactory alloc]
-          initWithBrowserState:browserState
-                  webStateList:[tabModel webStateList]];
-  return [[BrowserViewController alloc]
-                initWithTabModel:tabModel
-                    browserState:browserState
-               dependencyFactory:factory
-      applicationCommandEndpoint:_applicationCommandEndpoint];
+- (BrowserCoordinator*)coordinatorForBrowserState:
+                           (ios::ChromeBrowserState*)browserState
+                                         tabModel:(TabModel*)tabModel {
+  BrowserCoordinator* coordinator =
+      [[BrowserCoordinator alloc] initWithBaseViewController:nil
+                                                browserState:browserState];
+  coordinator.tabModel = tabModel;
+  coordinator.applicationCommandHandler = _applicationCommandEndpoint;
+  return coordinator;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.mm b/ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.mm
index 93e3268..1f2b35e 100644
--- a/ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.mm
+++ b/ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.mm
@@ -201,9 +201,7 @@
 // any changes.
 - (void)checkForContentInsetAdjustment {
   UIEdgeInsets inset = self.proxy.contentInset;
-  if (@available(iOS 11, *)) {
-    inset = self.proxy.adjustedContentInset;
-  }
+  inset = self.proxy.adjustedContentInset;
   if (!UIEdgeInsetsEqualToEdgeInsets(inset, self.updater.state.contentInset))
     [self.updater scrollViewDidResetContentInset:inset];
 }
diff --git a/ios/chrome/browser/ui/ntp/incognito_view.mm b/ios/chrome/browser/ui/ntp/incognito_view.mm
index 5f65456b..e256836 100644
--- a/ios/chrome/browser/ui/ntp/incognito_view.mm
+++ b/ios/chrome/browser/ui/ntp/incognito_view.mm
@@ -285,8 +285,7 @@
   if (!self.superview)
     return;
 
-  id<LayoutGuideProvider> safeAreaGuide =
-      SafeAreaLayoutGuideForView(self.superview);
+  id<LayoutGuideProvider> safeAreaGuide = self.superview.safeAreaLayoutGuide;
   _bottomUnsafeAreaGuideInSuperview = [[UILayoutGuide alloc] init];
   [self.superview addLayoutGuide:_bottomUnsafeAreaGuideInSuperview];
 
@@ -345,12 +344,7 @@
   if (IsRegularXRegularSizeClass(self)) {
     _topToolbarMarginHeight.constant = 0;
   } else {
-    CGFloat topInset = 0;
-    if (@available(iOS 11, *)) {
-      topInset = self.safeAreaInsets.top;
-    } else {
-      topInset = StatusBarHeight();
-    }
+    CGFloat topInset = self.safeAreaInsets.top;
     _topToolbarMarginHeight.constant =
         topInset + ToolbarExpandedHeight(
                        self.traitCollection.preferredContentSizeCategory);
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_egtest.mm b/ios/chrome/browser/ui/ntp/new_tab_page_egtest.mm
index 305dcd8..815a36b 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_egtest.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_egtest.mm
@@ -65,7 +65,7 @@
   [ChromeEarlGrey openNewIncognitoTab];
   WaitForHistoryToDisappear();
   chrome_test_util::VerifyAccessibilityForCurrentScreen();
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [ChromeEarlGrey closeAllIncognitoTabs];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm
index f7bacefb..a763b979 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.mm
@@ -50,7 +50,6 @@
 // When rendering the same string in a UITextField and a UILabel with the same
 // frame and the same font, the text is slightly offset.
 const CGFloat kUILabelUITextfieldBaselineDeltaInPoints = 1.0;
-const CGFloat kUILabelUITextfieldBaselineDeltaIpadIOS10InPixels = 1.0;
 
 // The default omnibox text color (used while editing).
 UIColor* TextColor() {
@@ -248,19 +247,6 @@
 
   NSTextAlignment alignment = [self bestTextAlignment];
   [self setTextAlignment:alignment];
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/730461): Remove this entire block once it's been tested
-    // on trunk.
-    UITextWritingDirection writingDirection =
-        alignment == NSTextAlignmentLeft ? UITextWritingDirectionLeftToRight
-                                         : UITextWritingDirectionRightToLeft;
-    [self
-        setBaseWritingDirection:writingDirection
-                       forRange:
-                           [self
-                               textRangeFromPosition:[self beginningOfDocument]
-                                          toPosition:[self endOfDocument]]];
-  }
 }
 
 - (UIColor*)displayedTextColor {
@@ -1118,19 +1104,12 @@
 - (void)layoutSelectionViewWithNewEditingRectBounds:(CGRect)newBounds {
   // The goal is to visually align the _selection label and the |self| textfield
   // to avoid text jumping when inline autocomplete is shown or hidden.
-  CGFloat baselineDifference = kUILabelUITextfieldBaselineDeltaInPoints;
-  if (IsIPadIdiom() && !base::ios::IsRunningOnIOS11OrLater()) {
-    // On iOS 10, there is a difference between iPad and iPhone rendering.
-    baselineDifference = kUILabelUITextfieldBaselineDeltaIpadIOS10InPixels /
-                         UIScreen.mainScreen.scale;
-  }
-
-  newBounds.origin.y -= baselineDifference;
+  newBounds.origin.y -= kUILabelUITextfieldBaselineDeltaInPoints;
 
   // Position the selection view appropriately.
   [_selection setFrame:newBounds];
 
-  newBounds.origin.y += baselineDifference;
+  newBounds.origin.y += kUILabelUITextfieldBaselineDeltaInPoints;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
index 5208e9e..e84c028 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
@@ -127,10 +127,7 @@
 - (void)viewDidLoad {
   [super viewDidLoad];
 
-  // Respect the safe area on iOS 11 to support iPhone X.
-  if (@available(iOS 11, *)) {
-    self.tableView.insetsContentViewsToSafeArea = YES;
-  }
+  self.tableView.insetsContentViewsToSafeArea = YES;
 
   // Initialize the same size as the parent view, autoresize will correct this.
   [self.view setFrame:CGRectZero];
@@ -168,15 +165,8 @@
   if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) {
     [self.tableView setLayoutMargins:UIEdgeInsetsZero];
   }
-  if (@available(iOS 11, *)) {
-    self.tableView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    self.automaticallyAdjustsScrollViewInsets = NO;
-  }
-#endif
+  self.tableView.contentInsetAdjustmentBehavior =
+      UIScrollViewContentInsetAdjustmentNever;
   [self.tableView setContentInset:UIEdgeInsetsMake(kTopAndBottomPadding, 0,
                                                    kTopAndBottomPadding, 0)];
   self.tableView.estimatedRowHeight = 0;
@@ -347,7 +337,7 @@
   [detailTextLabel setTextAlignment:_alignment];
 
   // The width must be positive for CGContextRef to be valid.
-  UIEdgeInsets safeAreaInsets = SafeAreaInsetsForView(self.view);
+  UIEdgeInsets safeAreaInsets = self.view.safeAreaInsets;
   CGRect rowBounds = UIEdgeInsetsInsetRect(self.view.bounds, safeAreaInsets);
   CGFloat labelWidth =
       MAX(40, floorf(rowBounds.size.width) - kTextCellLeadingPadding);
@@ -547,16 +537,8 @@
 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
   // TODO(crbug.com/733650): Default to the dragging check once it's been tested
   // on trunk.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    if (!scrollView.dragging)
-      return;
-  } else {
-    // Setting the top inset of the scrollView to |kTopAndBottomPadding| causes
-    // a one time scrollViewDidScroll to |-kTopAndBottomPadding|.  It's easier
-    // to just ignore this one scroll tick.
-    if (scrollView.contentOffset.y == 0 - kTopAndBottomPadding)
-      return;
-  }
+  if (!scrollView.dragging)
+    return;
 
   if (self.forwardsScrollEvents)
     [self.delegate autocompleteResultConsumerDidScroll:self];
diff --git a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
index 704ac5e..5d0ac33 100644
--- a/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
+++ b/ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.mm
@@ -386,12 +386,7 @@
   CGFloat topMargin = 0;
   if (!_webViewProxy && base::FeatureList::IsEnabled(
                             web::features::kBrowserContainerFullscreen)) {
-    CGFloat topMargin = 0.0;
-    if (@available(iOS 11, *)) {
-      topMargin = self.scrollView.safeAreaInsets.top;
-    } else {
-      topMargin = StatusBarHeight();
-    }
+    topMargin = self.scrollView.safeAreaInsets.top;
   }
   if (contentOffsetFromExpandedHeader >= 0) {
     // Record initial content offset and dispatch delegate on state change.
diff --git a/ios/chrome/browser/ui/payments/cells/page_info_item.mm b/ios/chrome/browser/ui/payments/cells/page_info_item.mm
index 56c0a366..690e2c4 100644
--- a/ios/chrome/browser/ui/payments/cells/page_info_item.mm
+++ b/ios/chrome/browser/ui/payments/cells/page_info_item.mm
@@ -68,15 +68,13 @@
     [text addAttribute:NSForegroundColorAttributeName
                  value:[[MDCPalette cr_greenPalette] tint700]
                  range:NSMakeRange(0, strlen(url::kHttpsScheme))];
-    if (@available(iOS 11, *)) {
-      // We need to set the font to the attributed portion, or the field doesn't
-      // asjust with dynamic types.
-      [text addAttribute:NSFontAttributeName
-                   value:[[UIFontMetrics defaultMetrics]
-                             scaledFontForFont:[[MDCTypography fontLoader]
-                                                   regularFontOfSize:12]]
-                   range:NSMakeRange(0, strlen(url::kHttpsScheme))];
-    }
+    // We need to set the font to the attributed portion, or the field doesn't
+    // asjust with dynamic types.
+    [text addAttribute:NSFontAttributeName
+                 value:[[UIFontMetrics defaultMetrics]
+                           scaledFontForFont:[[MDCTypography fontLoader]
+                                                 regularFontOfSize:12]]
+                 range:NSMakeRange(0, strlen(url::kHttpsScheme))];
     [cell.pageHostLabel setAttributedText:text];
 
     // Set lock image. UIImageRenderingModeAlwaysTemplate is used so that
diff --git a/ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.mm b/ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.mm
index 16801d23..3c01c660 100644
--- a/ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.mm
+++ b/ios/chrome/browser/ui/payments/cells/payments_selector_edit_item.mm
@@ -36,12 +36,8 @@
 
 - (UIFont*)nameFont {
   if (!_nameFont) {
-    if (@available(iOS 11, *)) {
-      _nameFont = [[UIFontMetrics defaultMetrics]
-          scaledFontForFont:[MDCTypography body2Font]];
-    } else {
-      _nameFont = [MDCTypography body2Font];
-    }
+    _nameFont = [[UIFontMetrics defaultMetrics]
+        scaledFontForFont:[MDCTypography body2Font]];
   }
   return _nameFont;
 }
@@ -55,12 +51,8 @@
 
 - (UIFont*)valueFont {
   if (!_valueFont) {
-    if (@available(iOS 11, *)) {
-      _valueFont = [[UIFontMetrics defaultMetrics]
-          scaledFontForFont:[MDCTypography body1Font]];
-    } else {
-      _valueFont = [MDCTypography body1Font];
-    }
+    _valueFont = [[UIFontMetrics defaultMetrics]
+        scaledFontForFont:[MDCTypography body1Font]];
   }
   return _valueFont;
 }
@@ -86,10 +78,8 @@
   cell.textLabel.textColor = self.nameColor;
   cell.detailTextLabel.font = self.valueFont;
   cell.detailTextLabel.textColor = self.valueColor;
-  if (@available(iOS 11, *)) {
-    cell.textLabel.adjustsFontForContentSizeCategory = YES;
-    cell.detailTextLabel.adjustsFontForContentSizeCategory = YES;
-  }
+  cell.textLabel.adjustsFontForContentSizeCategory = YES;
+  cell.detailTextLabel.adjustsFontForContentSizeCategory = YES;
 
   [cell updateConstraintsIfNeeded];
 }
diff --git a/ios/chrome/browser/ui/payments/payment_request_picker_view_controller.mm b/ios/chrome/browser/ui/payments/payment_request_picker_view_controller.mm
index 69ab08d..1605620 100644
--- a/ios/chrome/browser/ui/payments/payment_request_picker_view_controller.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_picker_view_controller.mm
@@ -286,12 +286,7 @@
     // content by the same amount, to ensure they line up properly. Also insets
     // by one more pixel to hide the one pixel gap left in between the
     // navigation bar and the UITableView.
-    CGFloat topInset = 0;
-    if (@available(iOS 11, *)) {
-      topInset = self.view.safeAreaInsets.top;
-    } else {
-      topInset = StatusBarHeight();
-    }
+    CGFloat topInset = self.view.safeAreaInsets.top;
     const UIEdgeInsets statusBarInset =
         UIEdgeInsetsMake(-1 - topInset, 0, 0, 0);
     self.tableView.contentInset = statusBarInset;
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_presenter.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_presenter.mm
index 1814f21..cf98023 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_presenter.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_presenter.mm
@@ -211,7 +211,7 @@
       constraintEqualToAnchor:namedGuide.centerXAnchor];
   center.priority = UILayoutPriorityDefaultHigh;
 
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(parentView);
+  id<LayoutGuideProvider> safeArea = parentView.safeAreaLayoutGuide;
   self.presentedConstraints = @[
     center,
     verticalPositioning,
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_table_view_controller.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_table_view_controller.mm
index 6b2843a20..42b4a4f 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_table_view_controller.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_table_view_controller.mm
@@ -139,15 +139,6 @@
       initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width,
                                0.01f)];
 
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // On iOS 10, a footer with a height of 0 is also needed to prevent inset at
-    // the bottom.
-    self.tableView.tableFooterView = [[UIView alloc]
-        initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width,
-                                 0.01f)];
-    self.tableView.sectionFooterHeight = 0.0;
-  }
-
   self.view.layer.cornerRadius = kPopupMenuCornerRadius;
   self.view.layer.masksToBounds = YES;
 }
diff --git a/ios/chrome/browser/ui/print/print_controller.mm b/ios/chrome/browser/ui/print/print_controller.mm
index 186c3ff..0e8cbc1 100644
--- a/ios/chrome/browser/ui/print/print_controller.mm
+++ b/ios/chrome/browser/ui/print/print_controller.mm
@@ -5,7 +5,7 @@
 #import "ios/chrome/browser/ui/print/print_controller.h"
 
 #import <MobileCoreServices/UTType.h>
-#import <Webkit/Webkit.h>
+#import <WebKit/WebKit.h>
 
 #include <memory>
 
diff --git a/ios/chrome/browser/ui/print/print_controller_egtest.mm b/ios/chrome/browser/ui/print/print_controller_egtest.mm
index 07fc678..1a7e9eb9 100644
--- a/ios/chrome/browser/ui/print/print_controller_egtest.mm
+++ b/ios/chrome/browser/ui/print/print_controller_egtest.mm
@@ -31,21 +31,6 @@
 
 // A test HTML URL.
 const char kHTMLURL[] = "http://test";
-
-// Returns the collection view for the activity services menu. Since this is a
-// system widget, it does not have an a11y id.  Instead, search for a
-// UICollectionView that is the superview of the "Copy" menu item.  There are
-// two nested UICollectionViews in the activity services menu, so choose the
-// innermost one.
-id<GREYMatcher> ShareMenuCollectionView() {
-  id<GREYMatcher> copyButton =
-      chrome_test_util::ButtonWithAccessibilityLabel(@"Copy");
-  return grey_allOf(
-      grey_kindOfClass([UICollectionView class]), grey_descendant(copyButton),
-      // Looking for a nested UICollectionView.
-      grey_descendant(grey_kindOfClass([UICollectionView class])), nil);
-}
-
 }  // namespace
 
 // Print test cases. These are Earl Grey integration tests.
@@ -64,27 +49,7 @@
 // loaded.
 // TODO(crbug.com/683280): Does this test serve any purpose on iOS11?
 - (void)testPrintNormalPage {
-  if (base::ios::IsRunningOnIOS11OrLater() && IsUIRefreshPhase1Enabled()) {
-    EARL_GREY_TEST_SKIPPED(
-        @"Dispatcher-based printing does not work on iOS11 when the "
-        @"UIRefresh flag is enabled.");
-  }
-
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/869477): Re-enable this test on device.
-    EARL_GREY_TEST_DISABLED(@"Fails on iOS 10.0 devices.");
-  }
-#endif
-
-#if TARGET_IPHONE_SIMULATOR
-  if (IsIPadIdiom() && !base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/871685): Re-enable this test.
-    EARL_GREY_TEST_DISABLED(
-        @"Failing on iOS 10 iPad simulator, for "
-        @"ios_chrome_multitasking_egtests");
-  }
-#endif  // TARGET_IPHONE_SIMULATOR
+  EARL_GREY_TEST_SKIPPED(@"Dispatcher-based printing does not work.");
 
   GURL url = web::test::HttpServer::MakeUrl(kHTMLURL);
   std::map<GURL, std::string> responses;
@@ -101,17 +66,7 @@
 // Tests that the AirPrint menu successfully loads when a PDF is loaded.
 // TODO(crbug.com/683280): Does this test serve any purpose on iOS11?
 - (void)testPrintPDF {
-  if (base::ios::IsRunningOnIOS11OrLater() && IsUIRefreshPhase1Enabled()) {
-    EARL_GREY_TEST_SKIPPED(
-        @"Dispatcher-based printing does not work on iOS11 when the "
-        @"UIRefresh flag is enabled.");
-  }
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    // TODO(crbug.com/869477): Re-enable this test on device.
-    EARL_GREY_TEST_DISABLED(@"Fails on iOS 10.0 devices.");
-  }
-#endif
+  EARL_GREY_TEST_SKIPPED(@"Dispatcher-based printing does not work.");
 
   web::test::SetUpFileBasedHttpServer();
   GURL url = web::test::HttpServer::MakeUrl(kPDFURL);
@@ -121,18 +76,6 @@
 }
 
 - (void)printCurrentPage {
-  // EarlGrey does not have the ability to interact with the share menu in
-  // iOS11, so use the dispatcher to trigger the print view controller instead.
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    [ChromeEarlGreyUI openShareMenu];
-    id<GREYMatcher> printButton =
-        chrome_test_util::ButtonWithAccessibilityLabel(@"Print");
-    [[[EarlGrey selectElementWithMatcher:printButton]
-           usingSearchAction:grey_scrollInDirection(kGREYDirectionRight, 100)
-        onElementWithMatcher:ShareMenuCollectionView()]
-        performAction:grey_tap()];
-  }
-
   id<GREYMatcher> printerOptionButton = grey_allOf(
       grey_accessibilityID(@"Printer Options"),
       grey_not(grey_accessibilityTrait(UIAccessibilityTraitHeader)), nil);
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
index 67ec84f..83a56ea 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
@@ -563,13 +563,6 @@
 // switched off and the correct button indicating that the torch is off is shown
 // when the scanner is opened again.
 - (void)testTorchButtonIsResetWhenQRScannerIsReopened {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
-
   id cameraControllerMock =
       [self getCameraControllerMockWithAuthorizationStatus:
                 AVAuthorizationStatusAuthorized];
@@ -603,12 +596,6 @@
 // Tests that the torch button is disabled when the camera reports that torch
 // became unavailable.
 - (void)testTorchButtonIsDisabledWhenTorchBecomesUnavailable {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
   id cameraControllerMock =
       [self getCameraControllerMockWithAuthorizationStatus:
                 AVAuthorizationStatusAuthorized];
@@ -656,12 +643,6 @@
 // Tests that a UIAlertController is presented by the QRScannerViewController if
 // the camera state changes after the QRScannerViewController is presented.
 - (void)testDialogIsDisplayedIfCameraStateChanges {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
   id cameraControllerMock =
       [self getCameraControllerMockWithAuthorizationStatus:
                 AVAuthorizationStatusAuthorized];
@@ -725,13 +706,6 @@
 
 // Tests that an error dialog is dismissed if the camera becomes available.
 - (void)testDialogDismissedIfCameraBecomesAvailable {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
-
   id cameraControllerMock =
       [self getCameraControllerMockWithAuthorizationStatus:
                 AVAuthorizationStatusAuthorized];
@@ -802,12 +776,6 @@
 
 // Test that the correct page is loaded if the scanner result is a URL.
 - (void)testReceivingQRScannerURLResult {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
   [self doTestReceivingResult:_testURL.GetContent()
                      response:kTestURLResponse
                          edit:nil];
@@ -816,16 +784,10 @@
 // Test that the correct page is loaded if the scanner result is a URL which is
 // then manually edited.
 - (void)testReceivingQRScannerURLResultAndEditingTheURL {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   [self doTestReceivingResult:_testURL.GetContent()
@@ -842,16 +804,10 @@
 // Test that the correct page is loaded if the scanner result is a search query
 // which is then manually edited.
 - (void)testReceivingQRScannerSearchQueryResultAndEditingTheQuery {
-// TODO(crbug.com/869176): Re-enable this test on iOS 10 iPad device.
-#if !TARGET_IPHONE_SIMULATOR
-  if (!base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 10 iPad device.");
-  }
-#endif
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   [self swizzleLocationBarCoordinatorLoadGURLFromLocationBar:_testQueryEdited];
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
index d2f60c4..d243d3c 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_egtest.mm
@@ -788,12 +788,7 @@
   TapContextMenuButtonWithA11yLabelID(
       IDS_IOS_READING_LIST_MARK_ALL_READ_ACTION);
 
-  if (@available(iOS 11, *)) {
-    // On iOS10, removing UITableView sections don't remove their header text
-    // from the hierarchy.
-    AssertHeaderNotVisible(kUnreadHeader);
-  }
-
+  AssertHeaderNotVisible(kUnreadHeader);
   AssertAllEntriesVisible();
   XCTAssertEqual(kNumberUnreadEntries + kNumberReadEntries,
                  ModelReadSize(GetReadingListModel()));
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index 0180c8ec5..f0753dba 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -138,21 +138,6 @@
   if (!editing) {
     self.editingWithToolbarButtons = NO;
     if (self.needsSectionCleanupAfterEditing) {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-variable"
-      // |removedSectionCount| is only used in iOS10, so unused variable
-      // warnings should be ignored in order to allow compilation until the
-      // iOS10 fix is removed.
-      NSUInteger removedSectionCount = [self removeEmptySections];
-#pragma clang diagnostic pop
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-      // The swipe-to-delete gesture on iOS 10 erroneously attempts to reload
-      // deleted row's section upon completion of its animation, regardless of
-      // whether the row was the last in the section.  If the section was
-      // removed force a full table reload so that the updated model is used.
-      if (removedSectionCount && !base::ios::IsRunningOnIOS11OrLater())
-        [self.tableView reloadData];
-#endif
       self.needsSectionCleanupAfterEditing = NO;
     }
   }
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
index e2fe73e..2dc4e83 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_egtest.mm
@@ -105,10 +105,7 @@
 
   // Close the tab containing the test page and wait for the stack view to
   // appear.
-  chrome_test_util::CloseCurrentTab();
-  // TODO(crbug.com/783192): ChromeEarlGrey should have a method to close the
-  // current tab and synchronize with the UI.
-  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+  [ChromeEarlGrey closeCurrentTab];
 
   // Open the Recent Tabs panel and check that the test page is present.
   OpenRecentTabsPanel();
@@ -151,7 +148,7 @@
   [[EarlGrey selectElementWithMatcher:exitMatcher] performAction:grey_tap()];
 
   // Close tab.
-  chrome_test_util::CloseCurrentTab();
+  [ChromeEarlGrey closeCurrentTab];
 }
 
 // Tests that the sign-in promo can be reloaded correctly.
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index b22cecd..6175cb1 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -560,29 +560,17 @@
     // sessionState.
     // Turn Off animations since UITableViewRowAnimationNone still animates.
     [UIView setAnimationsEnabled:NO];
-    // If iOS11+ use performBatchUpdates: instead of begin/endUpdates.
-    if (@available(iOS 11, *)) {
-      if (newSessionState ==
-          SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
-        [self.tableView performBatchUpdates:^{
-          [self updateSessionSections];
-        }
-                                 completion:nil];
-      } else {
-        [self.tableView performBatchUpdates:^{
-          [self updateOtherDevicesSectionForState:newSessionState];
-        }
-                                 completion:nil];
-      }
-    } else {
-      [self.tableView beginUpdates];
-      if (newSessionState ==
-          SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+    if (newSessionState ==
+        SessionsSyncUserState::USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS) {
+      [self.tableView performBatchUpdates:^{
         [self updateSessionSections];
-      } else {
+      }
+                               completion:nil];
+    } else {
+      [self.tableView performBatchUpdates:^{
         [self updateOtherDevicesSectionForState:newSessionState];
       }
-      [self.tableView endUpdates];
+                               completion:nil];
     }
     [UIView setAnimationsEnabled:YES];
   }
@@ -601,16 +589,10 @@
   if (!self.updatesTableView)
     return;
 
-  if (@available(iOS 11, *)) {
-    [self.tableView performBatchUpdates:^{
-      [self updateRecentlyClosedSection];
-    }
-                             completion:nil];
-  } else {
-    [self.tableView beginUpdates];
+  [self.tableView performBatchUpdates:^{
     [self updateRecentlyClosedSection];
-    [self.tableView endUpdates];
   }
+                           completion:nil];
 }
 
 - (void)setTabRestoreService:(sessions::TabRestoreService*)tabRestoreService {
@@ -651,18 +633,10 @@
   }
 }
 
-// TODO(crbug.com/850814): Use only dynamic sizing once we stop supporting
-// iOS10.
 - (CGFloat)tableView:(UITableView*)tableView
     heightForHeaderInSection:(NSInteger)section {
   DCHECK_EQ(tableView, self.tableView);
-  if (@available(iOS 11, *)) {
-    return UITableViewAutomaticDimension;
-  } else {
-    TableViewHeaderFooterItem* header =
-        [self.tableViewModel headerForSection:section];
-    return [header headerHeightForWidth:self.view.bounds.size.width];
-  }
+  return UITableViewAutomaticDimension;
 }
 
 - (CGFloat)tableView:(UITableView*)tableView
@@ -972,14 +946,7 @@
     }
   };
 
-  // If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
-  if (@available(iOS 11, *)) {
-    [self.tableView performBatchUpdates:tableUpdates completion:nil];
-  } else {
-    [self.tableView beginUpdates];
-    tableUpdates();
-    [self.tableView endUpdates];
-  }
+  [self.tableView performBatchUpdates:tableUpdates completion:nil];
 }
 
 #pragma mark - Long press and context menus
@@ -1085,21 +1052,10 @@
                   withRowAnimation:UITableViewRowAnimationLeft];
   };
 
-  // If iOS11+ use performBatchUpdates: instead of beginUpdates/endUpdates.
-  if (@available(iOS 11, *)) {
-    [self.tableView performBatchUpdates:tableUpdates
-                             completion:^(BOOL) {
-                               openTabs->DeleteForeignSession(sessionTagCopy);
-                             }];
-  } else {
-    [self.tableView beginUpdates];
-    tableUpdates();
-    // DeleteForeignSession will cause |self refreshUserState:| to be called,
-    // thus refreshing the TableView, running this inside the updates block will
-    // make sure that the tableView animations are performed in order.
-    openTabs->DeleteForeignSession(sessionTagCopy);
-    [self.tableView endUpdates];
-  }
+  [self.tableView performBatchUpdates:tableUpdates
+                           completion:^(BOOL) {
+                             openTabs->DeleteForeignSession(sessionTagCopy);
+                           }];
 }
 
 #pragma mark - SigninPromoViewConsumer
diff --git a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
index d78bc90..e022112 100644
--- a/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/save_passwords_collection_view_controller.mm
@@ -508,6 +508,9 @@
 
 - (void)onGetPasswordStoreResults:
     (std::vector<std::unique_ptr<autofill::PasswordForm>>&)result {
+  if (result.empty()) {
+    return;
+  }
   for (auto it = result.begin(); it != result.end(); ++it) {
     // PasswordForm is needed when user wants to delete the site/password.
     auto form = std::make_unique<autofill::PasswordForm>(**it);
diff --git a/ios/chrome/browser/ui/settings/settings_egtest.mm b/ios/chrome/browser/ui/settings/settings_egtest.mm
index 2f59237..56663449 100644
--- a/ios/chrome/browser/ui/settings/settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/settings_egtest.mm
@@ -614,7 +614,7 @@
 
   [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
       performAction:grey_tap()];
-  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [ChromeEarlGrey closeAllIncognitoTabs];
 }
 
 // Verifies the UI elements are accessible on the Settings page.
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
index 2e161977..1273533 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -52,8 +52,7 @@
 - (void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
 
-  id<LayoutGuideProvider> safeAreaLayoutGuide =
-      SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeAreaLayoutGuide = self.view.safeAreaLayoutGuide;
   UIView* contentView = self.contentViewController.view;
   UIView* headerView = self.appBarViewController.headerView;
   contentView.translatesAutoresizingMaskIntoConstraints = NO;
@@ -440,15 +439,10 @@
       viewController.navigationItem.leftBarButtonItems.count == 0) {
     viewController.navigationItem.leftBarButtonItem = [self backButton];
   }
-  // TODO(crbug.com/875528): This is a workaround for iOS 10.x.
-  if (@available(iOS 11, *)) {
-    // Wrap the view controller in an MDCAppBarContainerViewController if
-    // needed.
-    [super pushViewController:[self wrappedControllerIfNeeded:viewController]
-                     animated:animated];
-  } else {
-    [super pushViewController:viewController animated:animated];
-  }
+  // Wrap the view controller in an MDCAppBarContainerViewController if
+  // needed.
+  [super pushViewController:[self wrappedControllerIfNeeded:viewController]
+                   animated:animated];
 }
 
 - (UIViewController*)popViewControllerAnimated:(BOOL)animated {
diff --git a/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm b/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
index d4fb9bf..b88881b 100644
--- a/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
+++ b/ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm
@@ -96,12 +96,7 @@
     [self addSubview:background];
 
     [background setTranslatesAutoresizingMaskIntoConstraints:NO];
-    CGFloat topInset = 0;
-    if (@available(iOS 11, *)) {
-      topInset = self.safeAreaInsets.top;
-    } else {
-      topInset = StatusBarHeight();
-    }
+    CGFloat topInset = self.safeAreaInsets.top;
     self.backgroundTopConstraint =
         [[background topAnchor] constraintEqualToAnchor:self.topAnchor
                                                constant:-topInset];
@@ -136,12 +131,7 @@
 
 - (void)updateConstraints {
   [super updateConstraints];
-  CGFloat topInset = 0;
-  if (@available(iOS 11, *)) {
-    topInset = self.safeAreaInsets.top;
-  } else {
-    topInset = StatusBarHeight();
-  }
+  CGFloat topInset = self.safeAreaInsets.top;
   self.backgroundTopConstraint.constant = -topInset;
 }
 
diff --git a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
index 1f45746..fa917284 100644
--- a/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/grid/grid_view_controller.mm
@@ -178,7 +178,7 @@
   emptyStateView.translatesAutoresizingMaskIntoConstraints = NO;
   [self.collectionView.backgroundView addSubview:emptyStateView];
   id<LayoutGuideProvider> safeAreaGuide =
-      SafeAreaLayoutGuideForView(self.collectionView.backgroundView);
+      self.collectionView.backgroundView.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     [self.collectionView.backgroundView.centerXAnchor
         constraintEqualToAnchor:emptyStateView.centerXAnchor],
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_bottom_toolbar.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_bottom_toolbar.mm
index f974bbc..3bd0e3fd 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_bottom_toolbar.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_bottom_toolbar.mm
@@ -51,22 +51,10 @@
   UIButton* trailingButton = [UIButton buttonWithType:UIButtonTypeSystem];
   trailingButton.translatesAutoresizingMaskIntoConstraints = NO;
 
-  if (@available(iOS 11, *)) {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeading;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentTrailing;
-  } else if (base::i18n::IsRTL()) {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentRight;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeft;
-  } else {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeft;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentRight;
-  }
+  leadingButton.contentHorizontalAlignment =
+      UIControlContentHorizontalAlignmentLeading;
+  trailingButton.contentHorizontalAlignment =
+      UIControlContentHorizontalAlignmentTrailing;
 
   trailingButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor);
   TabGridNewTabButton* centerButton = [TabGridNewTabButton
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.mm
index 5f119f4..7bdff40 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_top_toolbar.mm
@@ -57,22 +57,10 @@
   trailingButton.translatesAutoresizingMaskIntoConstraints = NO;
   trailingButton.tintColor = UIColorFromRGB(kTabGridToolbarTextButtonColor);
 
-  if (@available(iOS 11, *)) {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeading;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentTrailing;
-  } else if (base::i18n::IsRTL()) {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentRight;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeft;
-  } else {
-    leadingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentLeft;
-    trailingButton.contentHorizontalAlignment =
-        UIControlContentHorizontalAlignmentRight;
-  }
+  leadingButton.contentHorizontalAlignment =
+      UIControlContentHorizontalAlignmentLeading;
+  trailingButton.contentHorizontalAlignment =
+      UIControlContentHorizontalAlignmentTrailing;
 
   [toolbar.contentView addSubview:leadingButton];
   [toolbar.contentView addSubview:trailingButton];
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
index f4a5a7fd..acecb42 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_egtest.mm
@@ -177,15 +177,14 @@
 
 // Tests entering the tab switcher by closing the last normal tab.
 - (void)testEnterSwitcherByClosingLastNormalTab {
-  chrome_test_util::CloseAllTabsInCurrentMode();
+  [ChromeEarlGrey closeAllTabsInCurrentMode];
 }
 
 // Tests entering the tab switcher by closing the last incognito tab.
 - (void)testEnterSwitcherByClosingLastIncognitoTab {
   [ChromeEarlGreyUI openNewIncognitoTab];
   [GetNormalTabModel() closeAllTabs];
-
-  chrome_test_util::CloseAllTabsInCurrentMode();
+  [ChromeEarlGrey closeAllTabsInCurrentMode];
 }
 
 // Tests exiting the switcher by tapping the switcher button.
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_handler.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_handler.mm
index 0e58ba49..8a2bc5c9 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_transition_handler.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_transition_handler.mm
@@ -23,12 +23,10 @@
 animationControllerForPresentedController:(UIViewController*)presented
                      presentingController:(UIViewController*)presenting
                          sourceController:(UIViewController*)source {
-  if (@available(iOS 11, *)) {
-    if (!UIAccessibilityIsReduceMotionEnabled() &&
-        self.provider.selectedCellVisible) {
-      return [[GridToVisibleTabAnimator alloc]
-          initWithStateProvider:self.provider];
-    }
+  if (!UIAccessibilityIsReduceMotionEnabled() &&
+      self.provider.selectedCellVisible) {
+    return
+        [[GridToVisibleTabAnimator alloc] initWithStateProvider:self.provider];
   }
   ReducedMotionAnimator* simpleAnimator = [[ReducedMotionAnimator alloc] init];
   simpleAnimator.presenting = YES;
@@ -37,10 +35,8 @@
 
 - (id<UIViewControllerAnimatedTransitioning>)
 animationControllerForDismissedController:(UIViewController*)dismissed {
-  if (@available(iOS 11, *)) {
-    if (!UIAccessibilityIsReduceMotionEnabled()) {
-      return [[TabToGridAnimator alloc] initWithStateProvider:self.provider];
-    }
+  if (!UIAccessibilityIsReduceMotionEnabled()) {
+    return [[TabToGridAnimator alloc] initWithStateProvider:self.provider];
   }
   ReducedMotionAnimator* simpleAnimator = [[ReducedMotionAnimator alloc] init];
   simpleAnimator.presenting = NO;
diff --git a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
index 36b1a08..258fbd7 100644
--- a/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_grid/tab_grid_view_controller.mm
@@ -459,33 +459,24 @@
                             : 0;
   UIEdgeInsets inset = UIEdgeInsetsMake(
       self.topToolbar.intrinsicContentSize.height, 0, bottomInset, 0);
-  if (@available(iOS 11, *)) {
-    // Left and right side could be missing correct safe area
-    // inset upon rotation. Manually correct it.
-    self.remoteTabsViewController.additionalSafeAreaInsets = UIEdgeInsetsZero;
-    UIEdgeInsets additionalSafeArea = inset;
-    UIEdgeInsets safeArea = self.scrollView.safeAreaInsets;
-    // If Remote Tabs isn't on the screen, it will not have the right safe area
-    // insets. Pass down the safe area insets of the scroll view.
-    if (self.currentPage != TabGridPageRemoteTabs) {
-      additionalSafeArea.right = safeArea.right;
-      additionalSafeArea.left = safeArea.left;
-    }
+  // Left and right side could be missing correct safe area
+  // inset upon rotation. Manually correct it.
+  self.remoteTabsViewController.additionalSafeAreaInsets = UIEdgeInsetsZero;
+  UIEdgeInsets additionalSafeArea = inset;
+  UIEdgeInsets safeArea = self.scrollView.safeAreaInsets;
+  // If Remote Tabs isn't on the screen, it will not have the right safe area
+  // insets. Pass down the safe area insets of the scroll view.
+  if (self.currentPage != TabGridPageRemoteTabs) {
+    additionalSafeArea.right = safeArea.right;
+    additionalSafeArea.left = safeArea.left;
+  }
 
-    // Ensure that the View Controller doesn't have safe area inset that already
-    // covers the view's bounds.
-    DCHECK(!CGRectIsEmpty(UIEdgeInsetsInsetRect(
-        self.remoteTabsViewController.tableView.bounds,
-        self.remoteTabsViewController.tableView.safeAreaInsets)));
-    self.remoteTabsViewController.additionalSafeAreaInsets = additionalSafeArea;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    // Must manually account for status bar in pre-iOS 11.
-    inset.top += self.topLayoutGuide.length;
-    self.remoteTabsViewController.tableView.contentInset = inset;
-  }
-#endif
+  // Ensure that the View Controller doesn't have safe area inset that already
+  // covers the view's bounds.
+  DCHECK(!CGRectIsEmpty(UIEdgeInsetsInsetRect(
+      self.remoteTabsViewController.tableView.bounds,
+      self.remoteTabsViewController.tableView.safeAreaInsets)));
+  self.remoteTabsViewController.additionalSafeAreaInsets = additionalSafeArea;
 }
 
 // Sets the proper insets for the Grid ViewControllers to accomodate for the
@@ -503,18 +494,10 @@
                             : 0;
   UIEdgeInsets inset = UIEdgeInsetsMake(
       self.topToolbar.intrinsicContentSize.height, 0, bottomInset, 0);
-  if (@available(iOS 11, *)) {
-    inset.left = self.scrollView.safeAreaInsets.left;
-    inset.right = self.scrollView.safeAreaInsets.right;
-    inset.top += self.scrollView.safeAreaInsets.top;
-    inset.bottom += self.scrollView.safeAreaInsets.bottom;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    // Must manually account for status bar in pre-iOS 11.
-    inset.top += self.topLayoutGuide.length;
-  }
-#endif
+  inset.left = self.scrollView.safeAreaInsets.left;
+  inset.right = self.scrollView.safeAreaInsets.right;
+  inset.top += self.scrollView.safeAreaInsets.top;
+  inset.bottom += self.scrollView.safeAreaInsets.bottom;
   self.incognitoTabsViewController.gridView.contentInset = inset;
   self.regularTabsViewController.gridView.contentInset = inset;
 }
@@ -615,12 +598,10 @@
   scrollView.scrollEnabled = YES;
   scrollView.pagingEnabled = YES;
   scrollView.delegate = self;
-  if (@available(iOS 11, *)) {
-    // Ensures that scroll view does not add additional margins based on safe
-    // areas.
-    scrollView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentNever;
-  }
+  // Ensures that scroll view does not add additional margins based on safe
+  // areas.
+  scrollView.contentInsetAdjustmentBehavior =
+      UIScrollViewContentInsetAdjustmentNever;
   UIView* contentView = [[UIView alloc] init];
   contentView.translatesAutoresizingMaskIntoConstraints = NO;
   [scrollView addSubview:contentView];
@@ -761,22 +742,10 @@
   ];
   [NSLayoutConstraint activateConstraints:constraints];
   // Set the height of the toolbar, including unsafe areas.
-  if (@available(iOS 11, *)) {
-    // SafeArea is only available in iOS  11+.
-    [topToolbar.bottomAnchor
-        constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor
-                       constant:topToolbar.intrinsicContentSize.height]
-        .active = YES;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    // Top and bottom layout guides are deprecated starting in iOS 11.
-    [topToolbar.bottomAnchor
-        constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor
-                       constant:topToolbar.intrinsicContentSize.height]
-        .active = YES;
-  }
-#endif
+  [topToolbar.bottomAnchor
+      constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor
+                     constant:topToolbar.intrinsicContentSize.height]
+      .active = YES;
 }
 
 // Adds the bottom toolbar and sets constraints.
@@ -794,22 +763,10 @@
   ];
   [NSLayoutConstraint activateConstraints:constraints];
   // Adds the height of the toolbar above the bottom safe area.
-  if (@available(iOS 11, *)) {
-    // SafeArea is only available in iOS  11+.
-    [self.view.safeAreaLayoutGuide.bottomAnchor
-        constraintEqualToAnchor:bottomToolbar.topAnchor
-                       constant:bottomToolbar.intrinsicContentSize.height]
-        .active = YES;
-  }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    // Top and bottom layout guides are deprecated starting in iOS 11.
-    [bottomToolbar.topAnchor
-        constraintEqualToAnchor:self.bottomLayoutGuide.topAnchor
-                       constant:-bottomToolbar.intrinsicContentSize.height]
-        .active = YES;
-  }
-#endif
+  [self.view.safeAreaLayoutGuide.bottomAnchor
+      constraintEqualToAnchor:bottomToolbar.topAnchor
+                     constant:bottomToolbar.intrinsicContentSize.height]
+      .active = YES;
 }
 
 // Adds floating button and constraints.
@@ -828,7 +785,7 @@
           UIUserInterfaceSizeClassRegular) {
     verticalInset = kTabGridFloatingButtonVerticalInsetLarge;
   }
-  id<LayoutGuideProvider> safeAreaGuide = SafeAreaLayoutGuideForView(self.view);
+  id<LayoutGuideProvider> safeAreaGuide = self.view.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     [button.trailingAnchor
         constraintEqualToAnchor:safeAreaGuide.trailingAnchor
diff --git a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
index d91b29a..608ccd89 100644
--- a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
+++ b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
@@ -153,16 +153,7 @@
 
 - (void)performBatchTableViewUpdates:(void (^)(void))updates
                           completion:(void (^)(BOOL finished))completion {
-  if (@available(iOS 11, *)) {
-    [self.tableView performBatchUpdates:updates completion:completion];
-  } else {
-    [self.tableView beginUpdates];
-    if (updates)
-      updates();
-    [self.tableView endUpdates];
-    if (completion)
-      completion(YES);
-  }
+  [self.tableView performBatchUpdates:updates completion:completion];
 }
 
 - (void)removeFromModelItemAtIndexPaths:(NSArray<NSIndexPath*>*)indexPaths {
diff --git a/ios/chrome/browser/ui/table_view/table_view_presentation_controller.mm b/ios/chrome/browser/ui/table_view/table_view_presentation_controller.mm
index 4eadca8..8eb5b47 100644
--- a/ios/chrome/browser/ui/table_view/table_view_presentation_controller.mm
+++ b/ios/chrome/browser/ui/table_view/table_view_presentation_controller.mm
@@ -65,12 +65,8 @@
 @synthesize tableViewContainer = _tableViewContainer;
 
 - (CGRect)frameOfPresentedViewInContainerView {
-  CGRect safeAreaBounds = self.containerView.bounds;
-  UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero;
-  if (@available(iOS 11, *)) {
-    safeAreaBounds = self.containerView.safeAreaLayoutGuide.layoutFrame;
-    safeAreaInsets = self.containerView.safeAreaInsets;
-  }
+  CGRect safeAreaBounds = self.containerView.safeAreaLayoutGuide.layoutFrame;
+  UIEdgeInsets safeAreaInsets = self.containerView.safeAreaInsets;
 
   CGFloat safeAreaWidth = CGRectGetWidth(safeAreaBounds);
   CGFloat safeAreaHeight = CGRectGetHeight(safeAreaBounds);
diff --git a/ios/chrome/browser/ui/tabs/tab_view.mm b/ios/chrome/browser/ui/tabs/tab_view.mm
index 54c0647..3ae798b 100644
--- a/ios/chrome/browser/ui/tabs/tab_view.mm
+++ b/ios/chrome/browser/ui/tabs/tab_view.mm
@@ -144,11 +144,9 @@
         forControlEvents:UIControlEventTouchUpInside];
 
     if (DragAndDropIsEnabled()) {
-      if (@available(iOS 11, *)) {
-        _dropInteraction =
-            [[DropAndNavigateInteraction alloc] initWithDelegate:self];
-        [self addInteraction:_dropInteraction];
-      }
+      _dropInteraction =
+          [[DropAndNavigateInteraction alloc] initWithDelegate:self];
+      [self addInteraction:_dropInteraction];
     }
   }
   return self;
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
index 4ebbe7b8..462424bd 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
@@ -604,10 +604,7 @@
             [window convertRect:element.frame fromView:element.superview]);
 
         CGFloat bottomSafeArea = CGFLOAT_MAX;
-        if (@available(iOS 11, *)) {
-          bottomSafeArea =
-              CGRectGetMaxY(window.safeAreaLayoutGuide.layoutFrame);
-        }
+        bottomSafeArea = CGRectGetMaxY(window.safeAreaLayoutGuide.layoutFrame);
         CGFloat infobarContentBottomPoint =
             MIN(bottomSafeArea, toolbarTopPoint);
         BOOL buttonIsAbove = buttonBottomPoint < infobarContentBottomPoint - 10;
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h
index a6068f5..beb3d56 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_DELEGATE_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_DELEGATE_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 @protocol ApplicationCommands;
 @protocol BrowserCommands;
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h
index dddf1f85..c4874de 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_VIEWS_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_VIEWS_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 @protocol ToolbarAssistiveKeyboardDelegate;
 
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h
index c24dbe4..311bc508 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_VIEWS_UTILS_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_ASSISTIVE_KEYBOARD_VIEWS_UTILS_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 @protocol ToolbarAssistiveKeyboardDelegate;
 
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_input_assistant_items.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_input_assistant_items.h
index 6bc31c4..2ed3671 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_input_assistant_items.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_input_assistant_items.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_INPUT_ASSISTANT_ITEMS_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_INPUT_ASSISTANT_ITEMS_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 @protocol ToolbarAssistiveKeyboardDelegate;
 
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.h
index 6af5def..2cff181 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_KEYBOARD_ACCESSORY_VIEW_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h"
 
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.mm b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.mm
index 9c68bb8..e007b79 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.mm
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_keyboard_accessory_view.mm
@@ -86,7 +86,7 @@
   [self addSubview:searchStackView];
 
   // Position the stack views.
-  id<LayoutGuideProvider> layoutGuide = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> layoutGuide = self.safeAreaLayoutGuide;
   [NSLayoutConstraint activateConstraints:@[
     [searchStackView.leadingAnchor
         constraintEqualToAnchor:layoutGuide.leadingAnchor
diff --git a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h
index fbcfcb61..3658ed1 100644
--- a/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h
+++ b/ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h
@@ -5,7 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_UI_BAR_BUTTON_ITEM_H_
 #define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_TOOLBAR_UI_BAR_BUTTON_ITEM_H_
 
-#import <UIKit/UIKIt.h>
+#import <UIKit/UIKit.h>
 
 @protocol ToolbarAssistiveKeyboardDelegate;
 
diff --git a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
index b726e650..c89d0ca 100644
--- a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
@@ -308,7 +308,7 @@
 
 // Sets the constraints up.
 - (void)setUpConstraints {
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> safeArea = self.safeAreaLayoutGuide;
   self.expandedConstraints = [NSMutableArray array];
   self.contractedConstraints = [NSMutableArray array];
   self.contractedNoMarginConstraints = [NSMutableArray array];
@@ -324,18 +324,6 @@
         constraintEqualToConstant:kAdaptiveToolbarButtonHeight],
   ]];
 
-  // When switching between incognito and non-incognito BVCs, it is possible for
-  // all of the toolbar's buttons to be temporarily hidden, which results in the
-  // stack view having zero width.  This seems to permanently break autolayout
-  // on iOS 10.  Adding an optional width constraint seems to work around this
-  // issue.  See https://crbug.com/851954.
-  if (!base::ios::IsRunningOnIOS11OrLater()) {
-    NSLayoutConstraint* minWidthConstraint =
-        [self.leadingStackView.widthAnchor constraintEqualToConstant:1.0];
-    minWidthConstraint.priority = UILayoutPriorityDefaultLow;
-    minWidthConstraint.active = YES;
-  }
-
   // LocationBar constraints. The constant value is set by the VC.
   self.locationBarHeight =
       [self.locationBarContainer.heightAnchor constraintEqualToConstant:0];
diff --git a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
index b6ad656..a5660c05 100644
--- a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
@@ -140,7 +140,7 @@
   self.stackView.translatesAutoresizingMaskIntoConstraints = NO;
   [contentView addSubview:self.stackView];
 
-  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self);
+  id<LayoutGuideProvider> safeArea = self.safeAreaLayoutGuide;
 
   [NSLayoutConstraint activateConstraints:@[
     [self.stackView.leadingAnchor
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
index a55d4ca..a9d2824 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_egtest.mm
@@ -150,11 +150,9 @@
     EARL_GREY_TEST_SKIPPED(@"Test not support on iPhone");
   }
 
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
-  }
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works on iOS 11.
+  EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
 
   const GURL URL = web::test::HttpServer::MakeUrl("http://origin");
 
@@ -249,46 +247,37 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
       assertWithMatcher:chrome_test_util::OmniboxText(URL.GetContent())];
 
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    // Can't access share menu from xctest on iOS 11+, so use the text field
-    // callout bar instead.
-    if (IsRefreshLocationBarEnabled()) {
-      [[EarlGrey
-          selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
-          performAction:grey_tap()];
-    } else {
-      [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
-          performAction:grey_tap()];
-    }
-    // Tap twice to get the pre-edit label callout bar copy button.
+  // Can't access share menu from xctest on iOS 11+, so use the text field
+  // callout bar instead.
+  if (IsRefreshLocationBarEnabled()) {
     [[EarlGrey
-        selectElementWithMatcher:grey_allOf(
-                                     grey_ancestor(chrome_test_util::Omnibox()),
-                                     grey_kindOfClass([UILabel class]), nil)]
+        selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
+        performAction:grey_tap()];
+  } else {
+    [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
+        performAction:grey_tap()];
+  }
+  // Tap twice to get the pre-edit label callout bar copy button.
+  [[EarlGrey
+      selectElementWithMatcher:grey_allOf(
+                                   grey_ancestor(chrome_test_util::Omnibox()),
+                                   grey_kindOfClass([UILabel class]), nil)]
+      performAction:grey_tap()];
+
+  [[[EarlGrey selectElementWithMatcher:SystemSelectionCalloutCopyButton()]
+      inRoot:SystemSelectionCallout()] performAction:grey_tap()];
+
+  if (IsIPadIdiom()) {
+    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Typing Shield")]
         performAction:grey_tap()];
 
-    [[[EarlGrey selectElementWithMatcher:SystemSelectionCalloutCopyButton()]
-        inRoot:SystemSelectionCallout()] performAction:grey_tap()];
-
-    if (IsIPadIdiom()) {
-      [[EarlGrey
-          selectElementWithMatcher:grey_accessibilityID(@"Typing Shield")]
-          performAction:grey_tap()];
-
-    } else {
-      // Typing shield might be unavailable if there are any suggestions
-      // displayed in the popup.
-      [[EarlGrey
-          selectElementWithMatcher:
-              grey_accessibilityID(kToolbarCancelOmniboxEditButtonIdentifier)]
-          performAction:grey_tap()];
-    }
-
   } else {
-    [ChromeEarlGreyUI openShareMenu];
+    // Typing shield might be unavailable if there are any suggestions
+    // displayed in the popup.
     [[EarlGrey
-        selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                     @"Copy")] performAction:grey_tap()];
+        selectElementWithMatcher:grey_accessibilityID(
+                                     kToolbarCancelOmniboxEditButtonIdentifier)]
+        performAction:grey_tap()];
   }
 
   [ChromeEarlGrey loadURL:secondURL];
@@ -307,10 +296,10 @@
 
 // Verifies that the clear text button clears any text in the omnibox.
 - (void)testOmniboxClearTextButton {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   const GURL URL = web::test::HttpServer::MakeUrl("http://origin");
diff --git a/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller.mm b/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller.mm
index 2e44a37..cad0e31 100644
--- a/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller.mm
+++ b/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller.mm
@@ -342,22 +342,11 @@
 
 // Adds additional height to the first toolbar to account for the safe area.
 - (void)updateForSafeArea {
-  if (@available(iOS 11, *)) {
-    if (self.orientation == ToolbarContainerOrientation::kTopToBottom) {
-      self.additionalStackHeight = self.view.safeAreaInsets.top;
-    } else {
-      self.additionalStackHeight = self.view.safeAreaInsets.bottom;
-    }
+  if (self.orientation == ToolbarContainerOrientation::kTopToBottom) {
+    self.additionalStackHeight = self.view.safeAreaInsets.top;
+  } else {
+    self.additionalStackHeight = self.view.safeAreaInsets.bottom;
   }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-  else {
-    if (self.orientation == ToolbarContainerOrientation::kTopToBottom) {
-      self.additionalStackHeight = self.topLayoutGuide.length;
-    } else {
-      self.additionalStackHeight = self.bottomLayoutGuide.length;
-    }
-  }
-#endif
 }
 
 @end
diff --git a/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller_unittest.mm b/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller_unittest.mm
index 2ac5f2e..80871a41 100644
--- a/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/toolbar_container/toolbar_container_view_controller_unittest.mm
@@ -129,32 +129,12 @@
     view_controller_.orientation =
         IsTopToBottom() ? ToolbarContainerOrientation::kTopToBottom
                         : ToolbarContainerOrientation::kBottomToTop;
-    if (@available(iOS 11, *)) {
-      UIEdgeInsets safe_insets = container_view().safeAreaInsets;
-      if (IsTopToBottom())
-        safe_insets.top = kSafeAreaStackInset - safe_insets.top;
-      else
-        safe_insets.bottom = kSafeAreaStackInset - safe_insets.bottom;
-      view_controller_.additionalSafeAreaInsets = safe_insets;
-    }
-#if !defined(__IPHONE_11_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0
-    else {
-      // Deactivate all pre-existing constraints for the |guide|'s height.
-      // They are added by UIKit at the maximum priority, so must be removed to
-      // update |guide|'s length.
-      id<UILayoutSupport> guide = IsTopToBottom()
-                                      ? view_controller_.topLayoutGuide
-                                      : view_controller_.bottomLayoutGuide;
-      for (NSLayoutConstraint* constraint in container_view().constraints) {
-        if (constraint.firstItem == guide &&
-            constraint.firstAttribute == NSLayoutAttributeHeight) {
-          constraint.active = NO;
-        }
-      }
-      [guide.heightAnchor constraintEqualToConstant:kSafeAreaStackInset]
-          .active = YES;
-    }
-#endif
+    UIEdgeInsets safe_insets = container_view().safeAreaInsets;
+    if (IsTopToBottom())
+      safe_insets.top = kSafeAreaStackInset - safe_insets.top;
+    else
+      safe_insets.bottom = kSafeAreaStackInset - safe_insets.bottom;
+    view_controller_.additionalSafeAreaInsets = safe_insets;
   }
 
   // Adds collapsible or non-collapsible toolbars to the container, depending on
diff --git a/ios/chrome/browser/ui/util/uikit_ui_util.h b/ios/chrome/browser/ui/util/uikit_ui_util.h
index 570f5e2f..1ba54b1 100644
--- a/ios/chrome/browser/ui/util/uikit_ui_util.h
+++ b/ios/chrome/browser/ui/util/uikit_ui_util.h
@@ -250,9 +250,6 @@
 // |type| represent the type of notification associated with this feedback.
 void TriggerHapticFeedbackForNotification(UINotificationFeedbackType type);
 
-// Returns the safeAreaInsets for a given view.
-UIEdgeInsets SafeAreaInsetsForView(UIView* view);
-
 // Returns the text for tabs count to be displayed in toolbar and tab_grid.
 // As an easter egg, show a smiley face instead of the count if the user has
 // more than 99 tabs open.
diff --git a/ios/chrome/browser/ui/util/uikit_ui_util.mm b/ios/chrome/browser/ui/util/uikit_ui_util.mm
index 33f0553..bb969d8 100644
--- a/ios/chrome/browser/ui/util/uikit_ui_util.mm
+++ b/ios/chrome/browser/ui/util/uikit_ui_util.mm
@@ -131,12 +131,8 @@
 }
 
 void SetUILabelScaledFont(UILabel* label, UIFont* font) {
-  if (@available(iOS 11, *)) {
-    label.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
-    label.adjustsFontForContentSizeCategory = YES;
-  } else {
-    label.font = font;
-  }
+  label.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
+  label.adjustsFontForContentSizeCategory = YES;
 }
 
 void MaybeSetUILabelScaledFont(BOOL maybe, UILabel* label, UIFont* font) {
@@ -148,12 +144,8 @@
 }
 
 void SetUITextFieldScaledFont(UITextField* textField, UIFont* font) {
-  if (@available(iOS 11, *)) {
-    textField.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
-    textField.adjustsFontForContentSizeCategory = YES;
-  } else {
-    textField.font = font;
-  }
+  textField.font = [[UIFontMetrics defaultMetrics] scaledFontForFont:font];
+  textField.adjustsFontForContentSizeCategory = YES;
 }
 
 void MaybeSetUITextFieldScaledFont(BOOL maybe,
@@ -649,13 +641,12 @@
 }
 
 UIResponder* GetFirstResponder() {
+  DCHECK_CURRENTLY_ON(web::WebThread::UI);
   UIApplication* application = [UIApplication sharedApplication];
-  if (base::ios::IsRunningOnIOS11OrLater() &&
-      base::FeatureList::IsEnabled(kFirstResponderKeyWindow)) {
+  if (base::FeatureList::IsEnabled(kFirstResponderKeyWindow)) {
     return GetFirstResponderSubview(application.keyWindow);
   }
 
-  DCHECK_CURRENTLY_ON(web::WebThread::UI);
   DCHECK(!gFirstResponder);
   [application sendAction:@selector(cr_markSelfCurrentFirstResponder)
                        to:nil
@@ -715,14 +706,6 @@
   }
 }
 
-UIEdgeInsets SafeAreaInsetsForView(UIView* view) {
-  if (@available(iOS 11, *)) {
-    return view.safeAreaInsets;
-  } else {
-    return UIEdgeInsetsZero;
-  }
-}
-
 NSString* TextForTabCount(long count) {
   if (count <= 0)
     return @"";
diff --git a/ios/chrome/browser/web/cache_egtest.mm b/ios/chrome/browser/web/cache_egtest.mm
index 8e8b64d..94a3626 100644
--- a/ios/chrome/browser/web/cache_egtest.mm
+++ b/ios/chrome/browser/web/cache_egtest.mm
@@ -190,10 +190,10 @@
 // Tests that cache is not used when selecting omnibox suggested website, even
 // though cache for that website exists.
 - (void)testCachingBehaviorOnSelectOmniboxSuggestion {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   web::test::SetUpHttpServer(std::make_unique<CacheTestResponseProvider>());
diff --git a/ios/chrome/browser/web/window_open_by_dom_egtest.mm b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
index 9124030..2c5c2d84 100644
--- a/ios/chrome/browser/web/window_open_by_dom_egtest.mm
+++ b/ios/chrome/browser/web/window_open_by_dom_egtest.mm
@@ -266,7 +266,7 @@
   [ChromeEarlGrey waitForMainTabCount:2];
 
   // Ensure that the starting tab hasn't navigated.
-  chrome_test_util::CloseCurrentTab();
+  [ChromeEarlGrey closeCurrentTab];
   const GURL URL = HttpServer::MakeUrl(kTestURL);
   [[EarlGrey selectElementWithMatcher:OmniboxText(URL.GetContent())]
       assertWithMatcher:grey_notNil()];
diff --git a/ios/chrome/common/ui_util/BUILD.gn b/ios/chrome/common/ui_util/BUILD.gn
index a2aba112..07f5302 100644
--- a/ios/chrome/common/ui_util/BUILD.gn
+++ b/ios/chrome/common/ui_util/BUILD.gn
@@ -12,19 +12,3 @@
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
 }
-
-source_set("unit_tests") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  testonly = true
-  sources = [
-    "constraints_ui_util_unittest.mm",
-  ]
-  deps = [
-    ":ui_util",
-    "//base",
-    "//ios/third_party/material_components_ios",
-    "//ios/third_party/material_roboto_font_loader_ios",
-    "//testing/gtest",
-    "//url",
-  ]
-}
diff --git a/ios/chrome/common/ui_util/constraints_ui_util.h b/ios/chrome/common/ui_util/constraints_ui_util.h
index e7a8616..9069c88 100644
--- a/ios/chrome/common/ui_util/constraints_ui_util.h
+++ b/ios/chrome/common/ui_util/constraints_ui_util.h
@@ -176,7 +176,4 @@
 
 #pragma mark - Safe Area.
 
-// Returns a safeAreaLayoutGuide for a given view.
-id<LayoutGuideProvider> SafeAreaLayoutGuideForView(UIView* view);
-
 #endif  // IOS_CHROME_COMMON_UI_UTIL_CONSTRAINTS_UI_UTIL_H_
diff --git a/ios/chrome/common/ui_util/constraints_ui_util.mm b/ios/chrome/common/ui_util/constraints_ui_util.mm
index 3d81c3c..fc110529 100644
--- a/ios/chrome/common/ui_util/constraints_ui_util.mm
+++ b/ios/chrome/common/ui_util/constraints_ui_util.mm
@@ -101,17 +101,7 @@
 }
 
 void PinToSafeArea(id<LayoutGuideProvider> innerView, UIView* outerView) {
-  id<LayoutGuideProvider> outerSafeAreaGuide =
-      SafeAreaLayoutGuideForView(outerView);
-  AddSameConstraints(innerView, outerSafeAreaGuide);
-}
-
-id<LayoutGuideProvider> SafeAreaLayoutGuideForView(UIView* view) {
-  if (@available(iOS 11, *)) {
-    return view.safeAreaLayoutGuide;
-  } else {
-    return view;
-  }
+  AddSameConstraints(innerView, outerView.safeAreaLayoutGuide);
 }
 
 void AddSameConstraintsToSides(id<LayoutGuideProvider> view1,
diff --git a/ios/chrome/common/ui_util/constraints_ui_util_unittest.mm b/ios/chrome/common/ui_util/constraints_ui_util_unittest.mm
deleted file mode 100644
index 8267e83..0000000
--- a/ios/chrome/common/ui_util/constraints_ui_util_unittest.mm
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/common/ui_util/constraints_ui_util.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-using ConstraintsUIUtilTest = PlatformTest;
-
-// Tests that SafeAreaLayoutGuideForView returns self on iOS 10.
-TEST_F(ConstraintsUIUtilTest, SafeAreaLayoutGuideForView) {
-  if (@available(iOS 11, *)) {
-  } else {
-    UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
-    EXPECT_EQ(view, SafeAreaLayoutGuideForView(view));
-  }
-}
-
-}  // namespace
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index caae9d15..1c3e4235 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -64,6 +64,17 @@
 // Opens a new incognito tab and waits for the new tab animation to complete.
 + (void)openNewIncognitoTab;
 
+// Closes all tabs in the current mode (incognito or normal), and waits for the
+// UI to complete. If current mode is Incognito, mode will be switched to 
+// normal after closing all tabs.
++ (void)closeAllTabsInCurrentMode;
+
+// Closes all incognito tabs and waits for the UI to complete.
++ (void)closeAllIncognitoTabs;
+
+// Closes the current tab and waits for the UI to complete.
++ (void)closeCurrentTab;
+
 // Waits for the page to finish loading within a timeout, or a GREYAssert is
 // induced.
 + (void)waitForPageToFinishLoading;
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index b5712fe..3f4d15d 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -143,6 +143,21 @@
   [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
 }
 
++ (void)closeAllTabsInCurrentMode {
+  chrome_test_util::CloseAllTabsInCurrentMode();
+  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+}
+
++ (void)closeAllIncognitoTabs {
+  GREYAssert(chrome_test_util::CloseAllIncognitoTabs(), @"Tabs did not close");
+  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+}
+
++ (void)closeCurrentTab {
+  chrome_test_util::CloseCurrentTab();
+  [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
+}
+
 + (void)waitForPageToFinishLoading {
   GREYAssert(chrome_test_util::WaitForPageToFinishLoading(),
              @"Page did not complete loading.");
diff --git a/ios/showcase/payments/sc_payments_picker_egtest.mm b/ios/showcase/payments/sc_payments_picker_egtest.mm
index 801a639..1741bdf 100644
--- a/ios/showcase/payments/sc_payments_picker_egtest.mm
+++ b/ios/showcase/payments/sc_payments_picker_egtest.mm
@@ -136,10 +136,10 @@
 
 // Tests if filtering works.
 - (void)testVerifyFiltering {
-  // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once
-  // grey_typeText works on iOS 11.
-  if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) {
-    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11.");
+  // TODO(crbug.com/753098): Re-enable this test on iPad once grey_typeText
+  // works.
+  if (IsIPadIdiom()) {
+    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
   }
 
   [self scrollToTop];
diff --git a/ios/showcase/test/showcase_eg_utils.mm b/ios/showcase/test/showcase_eg_utils.mm
index d86ec76..6b44ab5 100644
--- a/ios/showcase/test/showcase_eg_utils.mm
+++ b/ios/showcase/test/showcase_eg_utils.mm
@@ -16,17 +16,9 @@
 // Matcher for the back button on screens presented from the Showcase home
 // screen.
 id<GREYMatcher> BackButton() {
-  // TODO(crbug.com/750185): The original matcher fails on IOS 11 because the
-  // private class is not used anymore. Find a more robust solution that is
-  // consistent across different iOS versions.
-  if (base::ios::IsRunningOnIOS11OrLater()) {
-    return grey_allOf(grey_accessibilityLabel(@"SC"),
-                      grey_accessibilityTrait(UIAccessibilityTraitButton),
-                      grey_userInteractionEnabled(), nil);
-  }
-
-  return grey_kindOfClass(
-      NSClassFromString(@"_UINavigationBarBackIndicatorView"));
+  return grey_allOf(grey_accessibilityLabel(@"SC"),
+                    grey_accessibilityTrait(UIAccessibilityTraitButton),
+                    grey_userInteractionEnabled(), nil);
 }
 
 // Matcher for the Showcase home screen view.
diff --git a/ios/web/net/cookies/wk_http_system_cookie_store.h b/ios/web/net/cookies/wk_http_system_cookie_store.h
index 664e8bc..bd98c54 100644
--- a/ios/web/net/cookies/wk_http_system_cookie_store.h
+++ b/ios/web/net/cookies/wk_http_system_cookie_store.h
@@ -6,7 +6,7 @@
 #define IOS_WEB_NET_COOKIES_WK_HTTP_SYSTEM_COOKIE_STORE_H_
 
 #import <Foundation/Foundation.h>
-#import <WebKit/Webkit.h>
+#import <WebKit/WebKit.h>
 
 #import "ios/net/cookies/system_cookie_store.h"
 
diff --git a/ios/web/net/cookies/wk_http_system_cookie_store_unittest.mm b/ios/web/net/cookies/wk_http_system_cookie_store_unittest.mm
index 0f25ec1..ad1c1da2 100644
--- a/ios/web/net/cookies/wk_http_system_cookie_store_unittest.mm
+++ b/ios/web/net/cookies/wk_http_system_cookie_store_unittest.mm
@@ -5,7 +5,7 @@
 #import "ios/web/net/cookies/wk_http_system_cookie_store.h"
 
 #import <Foundation/Foundation.h>
-#import <WebKit/Webkit.h>
+#import <WebKit/WebKit.h>
 
 #include <memory>
 
diff --git a/media/gpu/jpeg_encode_accelerator_unittest.cc b/media/gpu/jpeg_encode_accelerator_unittest.cc
index bcdd1fea..893d3644d 100644
--- a/media/gpu/jpeg_encode_accelerator_unittest.cc
+++ b/media/gpu/jpeg_encode_accelerator_unittest.cc
@@ -1,11 +1,6 @@
 // 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.
-
-// This has to be included first.
-// See http://code.google.com/p/googletest/issues/detail?id=371
-#include "testing/gtest/include/gtest/gtest.h"
-
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
@@ -34,6 +29,7 @@
 #include "media/gpu/test/video_accelerator_unittest_helpers.h"
 #include "media/video/jpeg_encode_accelerator.h"
 #include "mojo/core/embedder/embedder.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/libyuv/include/libyuv.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 
@@ -47,9 +43,10 @@
 // Default test image file.
 const base::FilePath::CharType kDefaultYuvFilename[] =
     FILE_PATH_LITERAL("bali_640x360_P420.yuv:640x360");
-// Whether to save encode results to files. Output files will be saved
-// in the same directory with unittest. File name is like input file but
-// changing the extension to "jpg".
+// Whether to save encode results to files. Output files will be saved in the
+// same directory as the input files, with the '.jpg' extension appended to
+// their names. The encode result of generated images is written to the current
+// folder using HxW_[black|white].jpg as output file name.
 bool g_save_to_file = false;
 
 const double kMeanDiffThreshold = 10.0;
@@ -59,18 +56,21 @@
 class JpegEncodeAcceleratorTestEnvironment;
 JpegEncodeAcceleratorTestEnvironment* g_env;
 
-struct TestImageFile {
-  TestImageFile(const base::FilePath::StringType& filename,
-                gfx::Size visible_size)
-      : filename(filename), visible_size(visible_size) {}
+struct TestImage {
+  TestImage(std::vector<uint8_t> image_data,
+            const gfx::Size& visible_size,
+            const base::FilePath output_filename)
+      : image_data(std::move(image_data)),
+        visible_size(visible_size),
+        output_filename(output_filename) {}
 
-  base::FilePath::StringType filename;
-
-  // The input content of |filename|.
-  std::string data_str;
-
+  // Test image data.
+  std::vector<uint8_t> image_data;
   gfx::Size visible_size;
-  size_t output_size;
+
+  // Output filename, only used when '--save_to_file' is specified.
+  base::FilePath output_filename;
+  size_t output_size = 0;
 };
 
 enum class ClientState {
@@ -95,50 +95,41 @@
   void LogToFile(const std::string& key, const std::string& value);
 
   // Read image from |filename| to |image_data|.
-  void ReadTestYuvImage(base::FilePath& filename, TestImageFile* image_data);
+  std::unique_ptr<TestImage> ReadTestYuvImage(const base::FilePath& filename,
+                                              const gfx::Size& image_size);
 
   // Returns a file path for a file in what name specified or media/test/data
   // directory.  If the original file path is existed, returns it first.
   base::FilePath GetOriginalOrTestDataFilePath(const std::string& name);
 
   // Parsed data from command line.
-  std::vector<std::unique_ptr<TestImageFile>> image_data_user_;
+  std::vector<std::unique_ptr<TestImage>> image_data_user_;
 
-  // Parsed data of |test_4160x3120_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_4160x3120_white_;
-  // Parsed data of |test_2560x1920_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_2560x1920_white_;
+  // Generated 4160x3120 white I420 image.
+  std::unique_ptr<TestImage> image_data_4160x3120_white_;
+  // Generated 2560x1920 white I420 image.
+  std::unique_ptr<TestImage> image_data_2560x1920_white_;
   // Scarlet doesn't support 1080 width, it only suports 1088 width.
-  // Parsed data of |test_1280x720_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_1280x720_white_;
-  // Parsed data of |test_640x480_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_640x480_black_;
-  // Parsed data of |test_640x368_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_640x368_black_;
-  // Parsed data of |test_640x360_yuv_file_|.
-  std::unique_ptr<TestImageFile> image_data_640x360_black_;
+  // Generated 1280x720 white I420 image.
+  std::unique_ptr<TestImage> image_data_1280x720_white_;
+  // Generated 640x480 black I420 image.
+  std::unique_ptr<TestImage> image_data_640x480_black_;
+  // Generated 640x368 black I420 image.
+  std::unique_ptr<TestImage> image_data_640x368_black_;
+  // Generated 640x360 black I420 image.
+  std::unique_ptr<TestImage> image_data_640x360_black_;
 
   // Number of times SimpleEncodeTest should repeat for an image.
   const size_t repeat_;
 
  private:
-  // Create black or white test image with |width| and |height| size.
-  void CreateTestYuvImage(int width,
-                          int height,
-                          bool is_black,
-                          base::FilePath* filename);
+  // Create black or white test image with specified |size|.
+  std::unique_ptr<TestImage> CreateTestYuvImage(const gfx::Size& image_size,
+                                                bool is_black);
 
   const base::FilePath::CharType* user_yuv_files_;
   const base::FilePath log_path_;
   std::unique_ptr<base::File> log_file_;
-
-  // Programatically generated YUV files.
-  base::FilePath test_4160x3120_yuv_file_;
-  base::FilePath test_2560x1920_yuv_file_;
-  base::FilePath test_1280x720_yuv_file_;
-  base::FilePath test_640x480_yuv_file_;
-  base::FilePath test_640x360_yuv_file_;
-  base::FilePath test_640x368_yuv_file_;
 };
 
 void JpegEncodeAcceleratorTestEnvironment::SetUp() {
@@ -148,42 +139,14 @@
     LOG_ASSERT(log_file_->IsValid());
   }
 
-  CreateTestYuvImage(4160, 3120, false, &test_4160x3120_yuv_file_);
-  CreateTestYuvImage(2560, 1920, false, &test_2560x1920_yuv_file_);
-  CreateTestYuvImage(1280, 720, false, &test_1280x720_yuv_file_);
-  CreateTestYuvImage(640, 480, true, &test_640x480_yuv_file_);
-  CreateTestYuvImage(640, 360, true, &test_640x360_yuv_file_);
-  CreateTestYuvImage(640, 368, true, &test_640x368_yuv_file_);
-
-  image_data_4160x3120_white_.reset(new TestImageFile(
-      test_4160x3120_yuv_file_.value(), gfx::Size(4160, 3120)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_4160x3120_yuv_file_,
-                                           image_data_4160x3120_white_.get()));
-
-  image_data_2560x1920_white_.reset(new TestImageFile(
-      test_2560x1920_yuv_file_.value(), gfx::Size(2560, 1920)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_2560x1920_yuv_file_,
-                                           image_data_2560x1920_white_.get()));
-
-  image_data_1280x720_white_.reset(
-      new TestImageFile(test_1280x720_yuv_file_.value(), gfx::Size(1280, 720)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_1280x720_yuv_file_,
-                                           image_data_1280x720_white_.get()));
-
-  image_data_640x480_black_.reset(
-      new TestImageFile(test_640x480_yuv_file_.value(), gfx::Size(640, 480)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_640x480_yuv_file_,
-                                           image_data_640x480_black_.get()));
-
-  image_data_640x368_black_.reset(
-      new TestImageFile(test_640x368_yuv_file_.value(), gfx::Size(640, 368)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_640x368_yuv_file_,
-                                           image_data_640x368_black_.get()));
-
-  image_data_640x360_black_.reset(
-      new TestImageFile(test_640x360_yuv_file_.value(), gfx::Size(640, 360)));
-  ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(test_640x360_yuv_file_,
-                                           image_data_640x360_black_.get()));
+  image_data_4160x3120_white_ =
+      CreateTestYuvImage(gfx::Size(4160, 3120), false);
+  image_data_2560x1920_white_ =
+      CreateTestYuvImage(gfx::Size(2560, 1920), false);
+  image_data_1280x720_white_ = CreateTestYuvImage(gfx::Size(1280, 720), false);
+  image_data_640x480_black_ = CreateTestYuvImage(gfx::Size(640, 480), true);
+  image_data_640x368_black_ = CreateTestYuvImage(gfx::Size(640, 368), true);
+  image_data_640x360_black_ = CreateTestYuvImage(gfx::Size(640, 360), true);
 
   // |user_yuv_files_| may include many files and use ';' as delimiter.
   std::vector<base::FilePath::StringType> files =
@@ -209,21 +172,13 @@
     ASSERT_TRUE(!image_size.IsEmpty());
 
     base::FilePath input_file = GetOriginalOrTestDataFilePath(filename);
-    auto image_data = std::make_unique<TestImageFile>(filename, image_size);
-    ASSERT_NO_FATAL_FAILURE(ReadTestYuvImage(input_file, image_data.get()));
+    auto image_data = ReadTestYuvImage(input_file, image_size);
     image_data_user_.push_back(std::move(image_data));
   }
 }
 
 void JpegEncodeAcceleratorTestEnvironment::TearDown() {
   log_file_.reset();
-
-  base::DeleteFile(test_4160x3120_yuv_file_, false);
-  base::DeleteFile(test_2560x1920_yuv_file_, false);
-  base::DeleteFile(test_1280x720_yuv_file_, false);
-  base::DeleteFile(test_640x480_yuv_file_, false);
-  base::DeleteFile(test_640x368_yuv_file_, false);
-  base::DeleteFile(test_640x360_yuv_file_, false);
 }
 
 void JpegEncodeAcceleratorTestEnvironment::LogToFile(const std::string& key,
@@ -235,51 +190,56 @@
   }
 }
 
-void JpegEncodeAcceleratorTestEnvironment::CreateTestYuvImage(
-    int width,
-    int height,
-    bool is_black,
-    base::FilePath* filename) {
-  std::vector<uint8_t> buffer(width * height * 3 / 2);
+std::unique_ptr<TestImage>
+JpegEncodeAcceleratorTestEnvironment::CreateTestYuvImage(
+    const gfx::Size& image_size,
+    bool is_black) {
+  const size_t num_pixels = image_size.width() * image_size.height();
+  std::vector<uint8_t> image_data(num_pixels * 3 / 2);
 
-  size_t size = width * height;
   // Fill in Y values.
-  memset(buffer.data(), is_black ? 0 : 255, size);
-  // FIll in U and V values.
-  memset(buffer.data() + size, 128, size / 2);
-  LOG_ASSERT(base::CreateTemporaryFile(filename));
-  EXPECT_TRUE(base::AppendToFile(
-      *filename, reinterpret_cast<char*>(buffer.data()), buffer.size()));
+  std::fill(image_data.begin(), image_data.begin() + num_pixels,
+            is_black ? 0 : 255);
+  // Fill in U and V values.
+  std::fill(image_data.begin() + num_pixels, image_data.end(), 128);
+
+  base::FilePath output_filename(std::to_string(image_size.width()) + "x" +
+                                 std::to_string(image_size.height()) +
+                                 (is_black ? "_black.jpg" : "_white.jpg"));
+  return std::make_unique<TestImage>(std::move(image_data), image_size,
+                                     output_filename);
 }
 
-void JpegEncodeAcceleratorTestEnvironment::ReadTestYuvImage(
-    base::FilePath& input_file,
-    TestImageFile* image_data) {
-  ASSERT_TRUE(base::ReadFileToString(input_file, &image_data->data_str));
+std::unique_ptr<TestImage>
+JpegEncodeAcceleratorTestEnvironment::ReadTestYuvImage(
+    const base::FilePath& input_file,
+    const gfx::Size& image_size) {
+  int64_t file_size = 0;
+  LOG_ASSERT(GetFileSize(input_file, &file_size));
+  std::vector<uint8_t> image_data(file_size);
+  LOG_ASSERT(ReadFile(input_file, reinterpret_cast<char*>(image_data.data()),
+                      file_size) == file_size);
 
-  // This is just a placeholder. We will compute the real output size when we
-  // have encoder instance.
-  image_data->output_size =
-      VideoFrame::AllocationSize(PIXEL_FORMAT_I420, image_data->visible_size);
+  base::FilePath output_filename = input_file.AddExtension(".jpg");
+  return std::make_unique<TestImage>(std::move(image_data), image_size,
+                                     output_filename);
 }
 
 base::FilePath
 JpegEncodeAcceleratorTestEnvironment::GetOriginalOrTestDataFilePath(
     const std::string& name) {
-  base::FilePath original_file_path = base::FilePath(name);
-  base::FilePath return_file_path = GetTestDataFilePath(name);
-
-  if (PathExists(original_file_path))
-    return_file_path = original_file_path;
-
-  VLOG(3) << "Use file path " << return_file_path.value();
-  return return_file_path;
+  base::FilePath file_path = base::FilePath(name);
+  if (!PathExists(file_path)) {
+    file_path = GetTestDataFilePath(name);
+  }
+  VLOG(3) << "Using file path " << file_path.value();
+  return file_path;
 }
 
 class JpegClient : public JpegEncodeAccelerator::Client {
  public:
-  JpegClient(const std::vector<TestImageFile*>& test_aligned_image_files,
-             const std::vector<TestImageFile*>& test_image_files,
+  JpegClient(const std::vector<TestImage*>& test_aligned_images,
+             const std::vector<TestImage*>& test_images,
              ClientStateNotification<ClientState>* note);
   ~JpegClient() override;
   void CreateJpegEncoder();
@@ -293,10 +253,10 @@
 
  private:
   // Get the related test image file.
-  TestImageFile* GetTestImageFile(int32_t bitstream_buffer_id);
+  TestImage* GetTestImage(int32_t bitstream_buffer_id);
   void PrepareMemory(int32_t bitstream_buffer_id);
   void SetState(ClientState new_state);
-  void SaveToFile(TestImageFile* image_file, size_t hw_size, size_t sw_size);
+  void SaveToFile(TestImage* test_image, size_t hw_size, size_t sw_size);
   bool CompareHardwareAndSoftwareResults(int width,
                                          int height,
                                          size_t hw_encoded_size,
@@ -314,14 +274,14 @@
                                size_t* sw_encoded_size,
                                base::TimeDelta* sw_encode_time);
 
-  // JpegClient doesn't own |test_aligned_image_files_|.
+  // JpegClient doesn't own |test_aligned_images_|.
   // The resolutions of these images are all aligned. HW Accelerator must
   // support them.
-  const std::vector<TestImageFile*>& test_aligned_image_files_;
+  const std::vector<TestImage*>& test_aligned_images_;
 
-  // JpegClient doesn't own |test_image_files_|.
+  // JpegClient doesn't own |test_images_|.
   // The resolutions of these images may be unaligned.
-  const std::vector<TestImageFile*>& test_image_files_;
+  const std::vector<TestImage*>& test_images_;
 
   // A map that stores HW encoding start timestamp for each output buffer id.
   std::map<int, base::TimeTicks> buffer_id_to_start_time_;
@@ -346,12 +306,11 @@
   DISALLOW_COPY_AND_ASSIGN(JpegClient);
 };
 
-JpegClient::JpegClient(
-    const std::vector<TestImageFile*>& test_aligned_image_files,
-    const std::vector<TestImageFile*>& test_image_files,
-    ClientStateNotification<ClientState>* note)
-    : test_aligned_image_files_(test_aligned_image_files),
-      test_image_files_(test_image_files),
+JpegClient::JpegClient(const std::vector<TestImage*>& test_aligned_images,
+                       const std::vector<TestImage*>& test_images,
+                       ClientStateNotification<ClientState>* note)
+    : test_aligned_images_(test_aligned_images),
+      test_images_(test_images),
       state_(ClientState::CREATED),
       note_(note) {}
 
@@ -396,12 +355,11 @@
   base::TimeDelta elapsed_hw =
       hw_encode_end - buffer_id_to_start_time_[buffer_id];
 
-  TestImageFile* test_image;
-  if (buffer_id < static_cast<int32_t>(test_aligned_image_files_.size())) {
-    test_image = test_aligned_image_files_[buffer_id];
+  TestImage* test_image;
+  if (buffer_id < static_cast<int32_t>(test_aligned_images_.size())) {
+    test_image = test_aligned_images_[buffer_id];
   } else {
-    test_image =
-        test_image_files_[buffer_id - test_aligned_image_files_.size()];
+    test_image = test_images_[buffer_id - test_aligned_images_.size()];
   }
 
   size_t sw_encoded_size = 0;
@@ -519,44 +477,43 @@
   encoded_buffer_.reset(nullptr);
 }
 
-TestImageFile* JpegClient::GetTestImageFile(int32_t bitstream_buffer_id) {
+TestImage* JpegClient::GetTestImage(int32_t bitstream_buffer_id) {
   DCHECK_LT(static_cast<size_t>(bitstream_buffer_id),
-            test_aligned_image_files_.size() + test_image_files_.size());
-  TestImageFile* image_file;
-  if (bitstream_buffer_id <
-      static_cast<int32_t>(test_aligned_image_files_.size())) {
-    image_file = test_aligned_image_files_[bitstream_buffer_id];
+            test_aligned_images_.size() + test_images_.size());
+  TestImage* image_file;
+  if (bitstream_buffer_id < static_cast<int32_t>(test_aligned_images_.size())) {
+    image_file = test_aligned_images_[bitstream_buffer_id];
   } else {
-    image_file = test_image_files_[bitstream_buffer_id -
-                                   test_aligned_image_files_.size()];
+    image_file =
+        test_images_[bitstream_buffer_id - test_aligned_images_.size()];
   }
 
   return image_file;
 }
 
 void JpegClient::PrepareMemory(int32_t bitstream_buffer_id) {
-  TestImageFile* image_file = GetTestImageFile(bitstream_buffer_id);
+  TestImage* test_image = GetTestImage(bitstream_buffer_id);
 
-  size_t input_size = image_file->data_str.size();
+  size_t input_size = test_image->image_data.size();
   if (!in_shm_.get() || input_size > in_shm_->mapped_size()) {
     in_shm_.reset(new base::SharedMemory);
     LOG_ASSERT(in_shm_->CreateAndMapAnonymous(input_size));
   }
-  memcpy(in_shm_->memory(), image_file->data_str.data(), input_size);
+  memcpy(in_shm_->memory(), test_image->image_data.data(), input_size);
 
   if (!hw_out_shm_.get() ||
-      image_file->output_size > hw_out_shm_->mapped_size()) {
+      test_image->output_size > hw_out_shm_->mapped_size()) {
     hw_out_shm_.reset(new base::SharedMemory);
-    LOG_ASSERT(hw_out_shm_->CreateAndMapAnonymous(image_file->output_size));
+    LOG_ASSERT(hw_out_shm_->CreateAndMapAnonymous(test_image->output_size));
   }
-  memset(hw_out_shm_->memory(), 0, image_file->output_size);
+  memset(hw_out_shm_->memory(), 0, test_image->output_size);
 
   if (!sw_out_shm_.get() ||
-      image_file->output_size > sw_out_shm_->mapped_size()) {
+      test_image->output_size > sw_out_shm_->mapped_size()) {
     sw_out_shm_.reset(new base::SharedMemory);
-    LOG_ASSERT(sw_out_shm_->CreateAndMapAnonymous(image_file->output_size));
+    LOG_ASSERT(sw_out_shm_->CreateAndMapAnonymous(test_image->output_size));
   }
-  memset(sw_out_shm_->memory(), 0, image_file->output_size);
+  memset(sw_out_shm_->memory(), 0, test_image->output_size);
 }
 
 void JpegClient::SetState(ClientState new_state) {
@@ -568,39 +525,43 @@
   state_ = new_state;
 }
 
-void JpegClient::SaveToFile(TestImageFile* image_file,
+void JpegClient::SaveToFile(TestImage* test_image,
                             size_t hw_size,
                             size_t sw_size) {
-  DCHECK_NE(nullptr, image_file);
+  DCHECK_NE(nullptr, test_image);
 
-  base::FilePath in_filename(image_file->filename);
-  base::FilePath out_filename = in_filename.ReplaceExtension(".jpg");
+  base::FilePath out_filename_hw = test_image->output_filename;
+  LOG(INFO) << "Writing HW encode results to "
+            << out_filename_hw.MaybeAsASCII();
   ASSERT_EQ(
       static_cast<int>(hw_size),
-      base::WriteFile(out_filename, static_cast<char*>(hw_out_shm_->memory()),
-                      hw_size));
+      base::WriteFile(out_filename_hw,
+                      static_cast<char*>(hw_out_shm_->memory()), hw_size));
 
+  base::FilePath out_filename_sw = out_filename_hw.InsertBeforeExtension("_sw");
+  LOG(INFO) << "Writing SW encode results to "
+            << out_filename_sw.MaybeAsASCII();
   ASSERT_EQ(
       static_cast<int>(sw_size),
-      base::WriteFile(out_filename.InsertBeforeExtension("_sw"),
+      base::WriteFile(out_filename_sw,
                       static_cast<char*>(sw_out_shm_->memory()), sw_size));
 }
 
 void JpegClient::StartEncode(int32_t bitstream_buffer_id) {
-  TestImageFile* image_file = GetTestImageFile(bitstream_buffer_id);
+  TestImage* test_image = GetTestImage(bitstream_buffer_id);
 
-  image_file->output_size =
-      encoder_->GetMaxCodedBufferSize(image_file->visible_size);
+  test_image->output_size =
+      encoder_->GetMaxCodedBufferSize(test_image->visible_size);
   PrepareMemory(bitstream_buffer_id);
 
   base::SharedMemoryHandle dup_handle;
   dup_handle = base::SharedMemory::DuplicateHandle(hw_out_shm_->handle());
   encoded_buffer_ = std::make_unique<BitstreamBuffer>(
-      bitstream_buffer_id, dup_handle, image_file->output_size);
+      bitstream_buffer_id, dup_handle, test_image->output_size);
   scoped_refptr<VideoFrame> input_frame_ = VideoFrame::WrapExternalSharedMemory(
-      PIXEL_FORMAT_I420, image_file->visible_size,
-      gfx::Rect(image_file->visible_size), image_file->visible_size,
-      static_cast<uint8_t*>(in_shm_->memory()), image_file->data_str.size(),
+      PIXEL_FORMAT_I420, test_image->visible_size,
+      gfx::Rect(test_image->visible_size), test_image->visible_size,
+      static_cast<uint8_t*>(in_shm_->memory()), test_image->image_data.size(),
       in_shm_->handle(), 0, base::TimeDelta());
 
   LOG_ASSERT(input_frame_.get());
@@ -620,10 +581,10 @@
   // JpegEncodeAccelerator implementations.
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
-  // The elements of |test_aligned_image_files_| and |test_image_files_| are
+  // The elements of |test_aligned_images_| and |test_images_| are
   // owned by JpegEncodeAcceleratorTestEnvironment.
-  std::vector<TestImageFile*> test_aligned_image_files_;
-  std::vector<TestImageFile*> test_image_files_;
+  std::vector<TestImage*> test_aligned_images_;
+  std::vector<TestImage*> test_images_;
 
  protected:
   DISALLOW_COPY_AND_ASSIGN(JpegEncodeAcceleratorTest);
@@ -639,18 +600,18 @@
   for (size_t i = 0; i < num_concurrent_encoders; i++) {
     notes.push_back(std::make_unique<ClientStateNotification<ClientState>>());
     clients.push_back(std::make_unique<JpegClient>(
-        test_aligned_image_files_, test_image_files_, notes.back().get()));
+        test_aligned_images_, test_images_, notes.back().get()));
     encoder_thread.task_runner()->PostTask(
         FROM_HERE, base::BindOnce(&JpegClient::CreateJpegEncoder,
                                   base::Unretained(clients.back().get())));
     ASSERT_EQ(notes[i]->Wait(), ClientState::INITIALIZED);
   }
 
-  for (size_t index = 0; index < test_aligned_image_files_.size(); index++) {
-    VLOG(3) << index << ",width:"
-            << test_aligned_image_files_[index]->visible_size.width();
-    VLOG(3) << index << ",height:"
-            << test_aligned_image_files_[index]->visible_size.height();
+  for (size_t index = 0; index < test_aligned_images_.size(); index++) {
+    VLOG(3) << index
+            << ",width:" << test_aligned_images_[index]->visible_size.width();
+    VLOG(3) << index
+            << ",height:" << test_aligned_images_[index]->visible_size.height();
     for (size_t i = 0; i < num_concurrent_encoders; i++) {
       encoder_thread.task_runner()->PostTask(
           FROM_HERE, base::BindOnce(&JpegClient::StartEncode,
@@ -661,12 +622,12 @@
     }
   }
 
-  for (size_t index = 0; index < test_image_files_.size(); index++) {
-    int buffer_id = index + test_aligned_image_files_.size();
+  for (size_t index = 0; index < test_images_.size(); index++) {
+    int buffer_id = index + test_aligned_images_.size();
     VLOG(3) << buffer_id
-            << ",width:" << test_image_files_[index]->visible_size.width();
+            << ",width:" << test_images_[index]->visible_size.width();
     VLOG(3) << buffer_id
-            << ",height:" << test_image_files_[index]->visible_size.height();
+            << ",height:" << test_images_[index]->visible_size.height();
     for (size_t i = 0; i < num_concurrent_encoders; i++) {
       encoder_thread.task_runner()->PostTask(
           FROM_HERE,
@@ -698,7 +659,7 @@
 TEST_F(JpegEncodeAcceleratorTest, SimpleEncode) {
   for (size_t i = 0; i < g_env->repeat_; i++) {
     for (auto& image : g_env->image_data_user_) {
-      test_image_files_.push_back(image.get());
+      test_images_.push_back(image.get());
     }
   }
   TestEncode(1);
@@ -706,29 +667,28 @@
 
 TEST_F(JpegEncodeAcceleratorTest, MultipleEncoders) {
   for (auto& image : g_env->image_data_user_) {
-    test_image_files_.push_back(image.get());
+    test_images_.push_back(image.get());
   }
   TestEncode(3);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, ResolutionChange) {
-  test_image_files_.push_back(g_env->image_data_640x368_black_.get());
-  test_image_files_.push_back(g_env->image_data_640x360_black_.get());
-  test_aligned_image_files_.push_back(g_env->image_data_1280x720_white_.get());
+  test_images_.push_back(g_env->image_data_640x368_black_.get());
+  test_images_.push_back(g_env->image_data_640x360_black_.get());
+  test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
   TestEncode(1);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, AlignedSizes) {
-  test_aligned_image_files_.push_back(g_env->image_data_4160x3120_white_.get());
-  test_aligned_image_files_.push_back(g_env->image_data_2560x1920_white_.get());
-  test_aligned_image_files_.push_back(g_env->image_data_1280x720_white_.get());
-  test_aligned_image_files_.push_back(g_env->image_data_640x480_black_.get());
-
+  test_aligned_images_.push_back(g_env->image_data_4160x3120_white_.get());
+  test_aligned_images_.push_back(g_env->image_data_2560x1920_white_.get());
+  test_aligned_images_.push_back(g_env->image_data_1280x720_white_.get());
+  test_aligned_images_.push_back(g_env->image_data_640x480_black_.get());
   TestEncode(1);
 }
 
 TEST_F(JpegEncodeAcceleratorTest, CodedSizeAlignment) {
-  test_image_files_.push_back(g_env->image_data_640x360_black_.get());
+  test_images_.push_back(g_env->image_data_640x360_black_.get());
   TestEncode(1);
 }
 
diff --git a/media/gpu/vt_video_decode_accelerator_mac.cc b/media/gpu/vt_video_decode_accelerator_mac.cc
index bb650ed..95f1703 100644
--- a/media/gpu/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/vt_video_decode_accelerator_mac.cc
@@ -190,7 +190,7 @@
       &callback,       // output_callback
       session.InitializeInto());
   if (status) {
-    OSSTATUS_DLOG(WARNING, status) << "Failed to create VTDecompressionSession";
+    OSSTATUS_DVLOG(1, status) << "Failed to create VTDecompressionSession";
     return false;
   }
 
@@ -212,7 +212,7 @@
   const uint8_t pps_normal[] = {0x68, 0xe9, 0x7b, 0xcb};
   if (!CreateVideoToolboxSession(sps_normal, arraysize(sps_normal), pps_normal,
                                  arraysize(pps_normal), true)) {
-    DLOG(WARNING) << "Hardware decoding with VideoToolbox is not supported";
+    DVLOG(1) << "Hardware decoding with VideoToolbox is not supported";
     return false;
   }
 
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 0c7d2ab..e7a1159 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -269,7 +269,7 @@
 
 void MojoVideoDecoderService::OnDecoderInitialized(bool success) {
   DVLOG(1) << __func__;
-  DCHECK(decoder_);
+  DCHECK(!success || decoder_);
   DCHECK(init_cb_);
   TRACE_EVENT_ASYNC_END1("media", kInitializeTraceName, this, "success",
                          success);
diff --git a/media/mojo/test/mojo_video_decoder_integration_test.cc b/media/mojo/test/mojo_video_decoder_integration_test.cc
index a88979c..c9507b0 100644
--- a/media/mojo/test/mojo_video_decoder_integration_test.cc
+++ b/media/mojo/test/mojo_video_decoder_integration_test.cc
@@ -325,6 +325,32 @@
   EXPECT_EQ(client_->GetMaxDecodeRequests(), kMaxDecodeRequests);
 }
 
+TEST_F(MojoVideoDecoderIntegrationTest, InitializeFailNoDecoder) {
+  CreateClient();
+
+  StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
+  EXPECT_CALL(init_cb, Run(false));
+
+  // Clear |decoder_| so that Initialize() should fail.
+  decoder_.reset();
+  client_->Initialize(TestVideoConfig::NormalH264(), false, nullptr,
+                      init_cb.Get(), output_cb_.Get(), base::NullCallback());
+  RunUntilIdle();
+}
+
+TEST_F(MojoVideoDecoderIntegrationTest, InitializeFailNoCdm) {
+  CreateClient();
+
+  StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
+  EXPECT_CALL(init_cb, Run(false));
+
+  // CdmContext* (3rd parameter) is not provided but the VideoDecoderConfig
+  // specifies encrypted video, so Initialize() should fail.
+  client_->Initialize(TestVideoConfig::NormalEncrypted(), false, nullptr,
+                      init_cb.Get(), output_cb_.Get(), base::NullCallback());
+  RunUntilIdle();
+}
+
 TEST_F(MojoVideoDecoderIntegrationTest, MediaLogIsProxied) {
   ASSERT_TRUE(Initialize());
   EXPECT_MEDIA_LOG_ON(client_media_log_, HasSubstr("\"test\""));
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index 571d0cd..cd872e9 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -188,6 +188,7 @@
 
   deps = [
     "//net:net_deps",
+    "//net/dns/public",
   ]
   public_deps = [
     "//net:net_public_deps",
@@ -236,6 +237,7 @@
   deps = [
     ":host_resolver",
     "//net:net_deps",
+    "//net/dns/public",
   ]
   public_deps = [
     "//net:net_public_deps",
diff --git a/net/dns/dns_util.cc b/net/dns/dns_util.cc
index 6230aab1..9b3b5d0 100644
--- a/net/dns/dns_util.cc
+++ b/net/dns/dns_util.cc
@@ -99,7 +99,7 @@
 
   if (namelen + 1 > sizeof name)
     return false;
-  if (namelen == 0) // Empty names e.g. "", "." are not valid.
+  if (namelen == 0)  // Empty names e.g. "", "." are not valid.
     return false;
   name[namelen++] = 0;  // This is the root label (of length 0).
 
@@ -222,4 +222,30 @@
   return std::string(buf, sizeof(buf));
 }
 
+uint16_t DnsQueryTypeToQtype(DnsQueryType dns_query_type) {
+  switch (dns_query_type) {
+    case DnsQueryType::UNSPECIFIED:
+      NOTREACHED();
+      return 0;
+    case DnsQueryType::A:
+      return dns_protocol::kTypeA;
+    case DnsQueryType::AAAA:
+      return dns_protocol::kTypeAAAA;
+  }
+}
+
+DnsQueryType AddressFamilyToDnsQueryType(AddressFamily address_family) {
+  switch (address_family) {
+    case ADDRESS_FAMILY_UNSPECIFIED:
+      return DnsQueryType::UNSPECIFIED;
+    case ADDRESS_FAMILY_IPV4:
+      return DnsQueryType::A;
+    case ADDRESS_FAMILY_IPV6:
+      return DnsQueryType::AAAA;
+    default:
+      NOTREACHED();
+      return DnsQueryType::UNSPECIFIED;
+  }
+}
+
 }  // namespace net
diff --git a/net/dns/dns_util.h b/net/dns/dns_util.h
index 6694407..6e29748 100644
--- a/net/dns/dns_util.h
+++ b/net/dns/dns_util.h
@@ -9,8 +9,10 @@
 
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
+#include "net/base/address_family.h"
 #include "net/base/net_export.h"
 #include "net/base/network_change_notifier.h"
+#include "net/dns/public/dns_query_type.h"
 
 namespace net {
 
@@ -85,6 +87,12 @@
 // Note that |offset| must be less than 2^14 - 1 by definition.
 NET_EXPORT std::string CreateNamePointer(uint16_t offset);
 
+// Convert a DnsQueryType enum to the wire format integer representation.
+uint16_t DnsQueryTypeToQtype(DnsQueryType dns_query_type);
+
+NET_EXPORT DnsQueryType
+AddressFamilyToDnsQueryType(AddressFamily address_family);
+
 }  // namespace net
 
 #endif  // NET_DNS_DNS_UTIL_H_
diff --git a/net/dns/host_cache.cc b/net/dns/host_cache.cc
index 6807320..dc348add 100644
--- a/net/dns/host_cache.cc
+++ b/net/dns/host_cache.cc
@@ -16,6 +16,7 @@
 #include "net/base/net_errors.h"
 #include "net/base/trace_constants.h"
 #include "net/dns/dns_util.h"
+#include "net/dns/host_resolver.h"
 #include "net/log/net_log.h"
 
 namespace net {
@@ -34,7 +35,9 @@
 // String constants for dictionary keys.
 const char kHostnameKey[] = "hostname";
 const char kAddressFamilyKey[] = "address_family";
+const char kDnsQueryTypeKey[] = "dns_query_type";
 const char kFlagsKey[] = "flags";
+const char kHostResolverSourceKey[] = "host_resolver_source";
 const char kExpirationKey[] = "expiration";
 const char kTtlKey[] = "ttl";
 const char kNetworkChangesKey[] = "network_changes";
@@ -82,6 +85,26 @@
   MAX_ERASE_REASON
 };
 
+HostCache::Key::Key(const std::string& hostname,
+                    DnsQueryType dns_query_type,
+                    HostResolverFlags host_resolver_flags,
+                    HostResolverSource host_resolver_source)
+    : hostname(hostname),
+      dns_query_type(dns_query_type),
+      host_resolver_flags(host_resolver_flags),
+      host_resolver_source(host_resolver_source) {}
+
+HostCache::Key::Key(const std::string& hostname,
+                    AddressFamily address_family,
+                    HostResolverFlags host_resolver_flags)
+    : Key(hostname,
+          AddressFamilyToDnsQueryType(address_family),
+          host_resolver_flags,
+          HostResolverSource::ANY) {}
+
+HostCache::Key::Key()
+    : Key("", DnsQueryType::UNSPECIFIED, 0, HostResolverSource::ANY) {}
+
 HostCache::Entry::Entry(int error,
                         const AddressList& addresses,
                         Source source,
@@ -162,8 +185,7 @@
   RecordEraseAll(ERASE_DESTRUCT, tick_clock_->NowTicks());
 }
 
-const HostCache::Entry* HostCache::Lookup(const Key& key,
-                                          base::TimeTicks now) {
+const HostCache::Entry* HostCache::Lookup(const Key& key, base::TimeTicks now) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (caching_is_disabled())
     return nullptr;
@@ -318,9 +340,11 @@
         new base::DictionaryValue());
 
     entry_dict->SetString(kHostnameKey, key.hostname);
-    entry_dict->SetInteger(kAddressFamilyKey,
-                           static_cast<int>(key.address_family));
+    entry_dict->SetInteger(kDnsQueryTypeKey,
+                           static_cast<int>(key.dns_query_type));
     entry_dict->SetInteger(kFlagsKey, key.host_resolver_flags);
+    entry_dict->SetInteger(kHostResolverSourceKey,
+                           static_cast<int>(key.host_resolver_source));
 
     if (include_staleness) {
       // The kExpirationKey value is using TimeTicks instead of Time used if
@@ -362,8 +386,9 @@
       return false;
 
     std::string hostname;
-    int address_family;
+    int dns_query_type;
     HostResolverFlags flags;
+    int host_resolver_source;
     int error = OK;
     std::string expiration;
     base::ListValue empty_list;
@@ -372,11 +397,30 @@
 
     if (!entry_dict->GetString(kHostnameKey, &hostname) ||
         !entry_dict->GetInteger(kFlagsKey, &flags) ||
-        !entry_dict->GetInteger(kAddressFamilyKey, &address_family) ||
         !entry_dict->GetString(kExpirationKey, &expiration)) {
       return false;
     }
 
+    // If there is no DnsQueryType, look for an AddressFamily.
+    //
+    // TODO(crbug.com/846423): Remove kAddressFamilyKey support after a enough
+    // time has passed to minimize loss-of-persistence impact from backwards
+    // incompatibility.
+    if (!entry_dict->GetInteger(kDnsQueryTypeKey, &dns_query_type)) {
+      int address_family;
+      if (!entry_dict->GetInteger(kAddressFamilyKey, &address_family)) {
+        return false;
+      }
+      dns_query_type = static_cast<int>(AddressFamilyToDnsQueryType(
+          static_cast<AddressFamily>(address_family)));
+    }
+
+    // HostResolverSource is optional.
+    if (!entry_dict->GetInteger(kHostResolverSourceKey,
+                                &host_resolver_source)) {
+      host_resolver_source = static_cast<int>(HostResolverSource::ANY);
+    }
+
     // Only one of these fields should be in the dictionary.
     if (!entry_dict->GetInteger(kErrorKey, &error) &&
         !entry_dict->GetList(kAddressesKey, &addresses_value)) {
@@ -391,7 +435,8 @@
         tick_clock_->NowTicks() -
         (base::Time::Now() - base::Time::FromInternalValue(time_internal));
 
-    Key key(hostname, static_cast<AddressFamily>(address_family), flags);
+    Key key(hostname, static_cast<DnsQueryType>(dns_query_type), flags,
+            static_cast<HostResolverSource>(host_resolver_source));
     if (error == OK &&
         !AddressListFromListValue(addresses_value, &address_list)) {
       return false;
@@ -554,12 +599,12 @@
 
   const HostCache::Entry* entry =
       LookupStale(cache_key, tick_clock_->NowTicks(), stale_out);
-  if (!entry) {
+  if (!entry && IsAddressType(cache_key.dns_query_type)) {
     // Might not have found the cache entry because the address_family or
     // host_resolver_flags in cache_key do not match those used for the
     // original DNS lookup. Try another common combination of address_family
     // and host_resolver_flags in an attempt to find a matching cache entry.
-    cache_key.address_family = net::ADDRESS_FAMILY_IPV4;
+    cache_key.dns_query_type = DnsQueryType::A;
     cache_key.host_resolver_flags =
         net::HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
     entry = LookupStale(cache_key, tick_clock_->NowTicks(), stale_out);
diff --git a/net/dns/host_cache.h b/net/dns/host_cache.h
index 45a3ee2e..ffd63c14 100644
--- a/net/dns/host_cache.h
+++ b/net/dns/host_cache.h
@@ -23,6 +23,7 @@
 #include "net/base/net_export.h"
 #include "net/dns/dns_util.h"
 #include "net/dns/host_resolver_source.h"
+#include "net/dns/public/dns_query_type.h"
 
 namespace base {
 class ListValue;
@@ -34,34 +35,29 @@
 // Cache used by HostResolver to map hostnames to their resolved result.
 class NET_EXPORT HostCache {
  public:
-  struct Key {
+  struct NET_EXPORT Key {
+    Key(const std::string& hostname,
+        DnsQueryType dns_query_type,
+        HostResolverFlags host_resolver_flags,
+        HostResolverSource host_resolver_source);
     Key(const std::string& hostname,
         AddressFamily address_family,
-        HostResolverFlags host_resolver_flags,
-        HostResolverSource host_resolver_source = HostResolverSource::ANY)
-        : hostname(hostname),
-          address_family(address_family),
-          host_resolver_flags(host_resolver_flags),
-          host_resolver_source(host_resolver_source) {}
-
-    Key()
-        : address_family(ADDRESS_FAMILY_UNSPECIFIED),
-          host_resolver_flags(0),
-          host_resolver_source(HostResolverSource::ANY) {}
+        HostResolverFlags host_resolver_flags);
+    Key();
 
     bool operator<(const Key& other) const {
       // The order of comparisons of |Key| fields is arbitrary, thus
-      // |address_family| and |host_resolver_flags| are compared before
+      // |dns_query_type| and |host_resolver_flags| are compared before
       // |hostname| under assumption that integer comparisons are faster than
       // string comparisons.
-      return std::tie(address_family, host_resolver_flags, hostname,
+      return std::tie(dns_query_type, host_resolver_flags, hostname,
                       host_resolver_source) <
-             std::tie(other.address_family, other.host_resolver_flags,
+             std::tie(other.dns_query_type, other.host_resolver_flags,
                       other.hostname, other.host_resolver_source);
     }
 
     std::string hostname;
-    AddressFamily address_family;
+    DnsQueryType dns_query_type;
     HostResolverFlags host_resolver_flags;
     HostResolverSource host_resolver_source;
   };
diff --git a/net/dns/host_cache_unittest.cc b/net/dns/host_cache_unittest.cc
index 7f6e569..b75becc 100644
--- a/net/dns/host_cache_unittest.cc
+++ b/net/dns/host_cache_unittest.cc
@@ -23,9 +23,10 @@
 
 const int kMaxCacheEntries = 10;
 
-// Builds a key for |hostname|, defaulting the address family to unspecified.
+// Builds a key for |hostname|, defaulting the query type to unspecified.
 HostCache::Key Key(const std::string& hostname) {
-  return HostCache::Key(hostname, ADDRESS_FAMILY_UNSPECIFIED, 0);
+  return HostCache::Key(hostname, DnsQueryType::UNSPECIFIED, 0,
+                        HostResolverSource::ANY);
 }
 
 bool FoobarIndexIsOdd(const std::string& foobarx_com) {
@@ -199,8 +200,8 @@
 }
 
 // Tests that the same hostname can be duplicated in the cache, so long as
-// the address family differs.
-TEST(HostCacheTest, AddressFamilyIsPartOfKey) {
+// the query type differs.
+TEST(HostCacheTest, DnsQueryTypeIsPartOfKey) {
   const base::TimeDelta kSuccessEntryTTL = base::TimeDelta::FromSeconds(10);
 
   HostCache cache(kMaxCacheEntries);
@@ -208,8 +209,10 @@
   // t=0.
   base::TimeTicks now;
 
-  HostCache::Key key1("foobar.com", ADDRESS_FAMILY_UNSPECIFIED, 0);
-  HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4, 0);
+  HostCache::Key key1("foobar.com", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY);
+  HostCache::Key key2("foobar.com", DnsQueryType::A, 0,
+                      HostResolverSource::ANY);
   HostCache::Entry entry =
       HostCache::Entry(OK, AddressList(), HostCache::Entry::SOURCE_UNKNOWN);
 
@@ -242,11 +245,12 @@
   // t=0.
   base::TimeTicks now;
 
-  HostCache::Key key1("foobar.com", ADDRESS_FAMILY_IPV4, 0);
-  HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4,
-                      HOST_RESOLVER_CANONNAME);
-  HostCache::Key key3("foobar.com", ADDRESS_FAMILY_IPV4,
-                      HOST_RESOLVER_LOOPBACK_ONLY);
+  HostCache::Key key1("foobar.com", DnsQueryType::A, 0,
+                      HostResolverSource::ANY);
+  HostCache::Key key2("foobar.com", DnsQueryType::A, HOST_RESOLVER_CANONNAME,
+                      HostResolverSource::ANY);
+  HostCache::Key key3("foobar.com", DnsQueryType::A,
+                      HOST_RESOLVER_LOOPBACK_ONLY, HostResolverSource::ANY);
   HostCache::Entry entry =
       HostCache::Entry(OK, AddressList(), HostCache::Entry::SOURCE_UNKNOWN);
 
@@ -536,55 +540,47 @@
     //    1 means key1 is greater than key2
     int expected_comparison;
   } tests[] = {
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      0
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
-      -1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      -1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
-      HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      HostCache::Key("host2", ADDRESS_FAMILY_IPV4, 0),
-      -1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
-                     HOST_RESOLVER_CANONNAME),
-      -1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
-                     HOST_RESOLVER_CANONNAME),
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
-      1
-    },
-    {
-      HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
-                     HOST_RESOLVER_CANONNAME),
-      HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED,
-                     HOST_RESOLVER_CANONNAME),
-      -1
-    },
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       0},
+      {HostCache::Key("host1", DnsQueryType::A, 0, HostResolverSource::ANY),
+       HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       HostCache::Key("host1", DnsQueryType::A, 0, HostResolverSource::ANY),
+       -1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       HostCache::Key("host2", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       -1},
+      {HostCache::Key("host1", DnsQueryType::A, 0, HostResolverSource::ANY),
+       HostCache::Key("host2", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       HostCache::Key("host2", DnsQueryType::A, 0, HostResolverSource::ANY),
+       -1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       HostCache::Key("host1", DnsQueryType::UNSPECIFIED,
+                      HOST_RESOLVER_CANONNAME, HostResolverSource::ANY),
+       -1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED,
+                      HOST_RESOLVER_CANONNAME, HostResolverSource::ANY),
+       HostCache::Key("host1", DnsQueryType::UNSPECIFIED, 0,
+                      HostResolverSource::ANY),
+       1},
+      {HostCache::Key("host1", DnsQueryType::UNSPECIFIED,
+                      HOST_RESOLVER_CANONNAME, HostResolverSource::ANY),
+       HostCache::Key("host2", DnsQueryType::UNSPECIFIED,
+                      HOST_RESOLVER_CANONNAME, HostResolverSource::ANY),
+       -1},
   };
 
   for (size_t i = 0; i < arraysize(tests); ++i) {
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc
index 46afc19..9b8daa3 100644
--- a/net/dns/host_resolver.cc
+++ b/net/dns/host_resolver.cc
@@ -11,6 +11,7 @@
 #include "base/values.h"
 #include "net/base/net_errors.h"
 #include "net/dns/dns_client.h"
+#include "net/dns/dns_util.h"
 #include "net/dns/host_cache.h"
 #include "net/dns/host_resolver_impl.h"
 
@@ -39,8 +40,8 @@
   limits.total_jobs = kDefaultMaxProcTasks;
 
   // Parallelism is determined by the field trial.
-  std::string group = base::FieldTrialList::FindFullName(
-      "HostResolverDispatch");
+  std::string group =
+      base::FieldTrialList::FindFullName("HostResolverDispatch");
 
   if (group.empty())
     return limits;
@@ -86,8 +87,7 @@
 HostResolver::Options::Options()
     : max_concurrent_resolves(kDefaultParallelism),
       max_retry_attempts(kDefaultRetryAttempts),
-      enable_caching(true) {
-}
+      enable_caching(true) {}
 
 std::unique_ptr<HostResolver> HostResolver::Factory::CreateResolver(
     const Options& options,
@@ -114,8 +114,7 @@
 
 HostResolver::~HostResolver() = default;
 
-void HostResolver::SetDnsClientEnabled(bool enabled) {
-}
+void HostResolver::SetDnsClientEnabled(bool enabled) {}
 
 HostCache* HostResolver::GetHostCache() {
   return nullptr;
@@ -189,22 +188,6 @@
 }
 
 // static
-HostResolver::DnsQueryType HostResolver::AddressFamilyToDnsQueryType(
-    AddressFamily address_family) {
-  switch (address_family) {
-    case ADDRESS_FAMILY_UNSPECIFIED:
-      return DnsQueryType::UNSPECIFIED;
-    case ADDRESS_FAMILY_IPV4:
-      return DnsQueryType::A;
-    case ADDRESS_FAMILY_IPV6:
-      return DnsQueryType::AAAA;
-    default:
-      NOTREACHED();
-      return DnsQueryType::UNSPECIFIED;
-  }
-}
-
-// static
 HostResolver::ResolveHostParameters
 HostResolver::RequestInfoToResolveHostParameters(
     const HostResolver::RequestInfo& request_info,
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index 27d2967..157d757 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -21,6 +21,7 @@
 #include "net/dns/dns_config.h"
 #include "net/dns/host_cache.h"
 #include "net/dns/host_resolver_source.h"
+#include "net/dns/public/dns_query_type.h"
 
 namespace base {
 class Value;
@@ -88,9 +89,9 @@
     // If cancelled before |callback| is invoked, it will never be invoked.
     virtual int Start(CompletionOnceCallback callback) = 0;
 
-    // Result of the request. Should only be called after Start() signals
-    // completion, either by invoking the callback or by returning a result
-    // other than |ERR_IO_PENDING|.
+    // Address record (A or AAAA) results of the request. Should only be called
+    // after Start() signals completion, either by invoking the callback or by
+    // returning a result other than |ERR_IO_PENDING|.
     //
     // TODO(crbug.com/821021): Implement other GetResults() methods for requests
     // that return other data (eg DNS TXT requests).
@@ -188,17 +189,6 @@
     bool is_my_ip_address_;
   };
 
-  // DNS query type for a ResolveHostRequest.
-  // See:
-  // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
-  //
-  // TODO(crbug.com/846423): Add support for non-address types.
-  enum class DnsQueryType {
-    UNSPECIFIED,
-    A,
-    AAAA,
-  };
-
   // Parameter-grouping struct for additional optional parameters for
   // CreateRequest() calls. All fields are optional and have a reasonable
   // default.
@@ -379,7 +369,6 @@
   //
   // TODO(crbug.com/821021): Delete these methods once all usage has been
   // converted to the new CreateRequest() API.
-  static DnsQueryType AddressFamilyToDnsQueryType(AddressFamily address_family);
   static ResolveHostParameters RequestInfoToResolveHostParameters(
       const RequestInfo& request_info,
       RequestPriority priority);
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index 818c55f..47cf9efd 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -21,6 +21,7 @@
 #include <cmath>
 #include <limits>
 #include <memory>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -513,7 +514,7 @@
 // Is |dns_server| within the list of known DNS servers that also support
 // DNS-over-HTTPS?
 bool DnsServerSupportsDoh(const IPAddress& dns_server) {
-  const static base::NoDestructor<std::unordered_set<std::string>>
+  static const base::NoDestructor<std::unordered_set<std::string>>
       upgradable_servers({
           // Google Public DNS
           "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844",
@@ -805,6 +806,9 @@
         net_log_(job_net_log),
         tick_clock_(tick_clock),
         weak_ptr_factory_(this) {
+    // ProcTask only supports resolving addresses.
+    DCHECK(IsAddressType(key_.dns_query_type));
+
     DCHECK(callback_);
     if (!params_.resolver_proc.get())
       params_.resolver_proc = HostResolverProc::GetDefault();
@@ -884,9 +888,9 @@
       AttemptCompletionCallback completion_callback) {
     AddressList results;
     int os_error = 0;
-    int error =
-        resolver_proc->Resolve(key.hostname, key.address_family,
-                               key.host_resolver_flags, &results, &os_error);
+    int error = resolver_proc->Resolve(
+        key.hostname, DnsQueryTypeToAddressFamily(key.dns_query_type),
+        key.host_resolver_flags, &results, &os_error);
 
     network_task_runner->PostTask(
         FROM_HERE, base::BindOnce(std::move(completion_callback), results,
@@ -1039,52 +1043,41 @@
   bool allow_fallback_resolution() const { return allow_fallback_resolution_; }
 
   bool needs_two_transactions() const {
-    return key_.address_family == ADDRESS_FAMILY_UNSPECIFIED;
+    return key_.dns_query_type == DnsQueryType::UNSPECIFIED;
   }
 
   bool needs_another_transaction() const {
-    return needs_two_transactions() && !transaction_aaaa_;
+    return needs_two_transactions() && !transaction2_;
   }
 
   void StartFirstTransaction() {
     DCHECK_EQ(0u, num_completed_transactions_);
+    DCHECK(!transaction1_);
+
     net_log_.BeginEvent(NetLogEventType::HOST_RESOLVER_IMPL_DNS_TASK);
-    if (key_.address_family == ADDRESS_FAMILY_IPV6) {
-      StartAAAA();
+    if (key_.dns_query_type == DnsQueryType::UNSPECIFIED) {
+      transaction1_ = CreateTransaction(DnsQueryType::A);
     } else {
-      StartA();
+      transaction1_ = CreateTransaction(key_.dns_query_type);
     }
+    transaction1_->Start();
   }
 
   void StartSecondTransaction() {
-    DCHECK(needs_two_transactions());
-    StartAAAA();
+    DCHECK(needs_another_transaction());
+    transaction2_ = CreateTransaction(DnsQueryType::AAAA);
+    transaction2_->Start();
   }
 
   base::TimeDelta ttl() { return ttl_; }
 
  private:
-  void StartA() {
-    DCHECK(!transaction_a_);
-    DCHECK_NE(ADDRESS_FAMILY_IPV6, key_.address_family);
-    transaction_a_ = CreateTransaction(ADDRESS_FAMILY_IPV4);
-    transaction_a_->Start();
-  }
-
-  void StartAAAA() {
-    DCHECK(!transaction_aaaa_);
-    DCHECK_NE(ADDRESS_FAMILY_IPV4, key_.address_family);
-    transaction_aaaa_ = CreateTransaction(ADDRESS_FAMILY_IPV6);
-    transaction_aaaa_->Start();
-  }
-
-  std::unique_ptr<DnsTransaction> CreateTransaction(AddressFamily family) {
-    DCHECK_NE(ADDRESS_FAMILY_UNSPECIFIED, family);
+  std::unique_ptr<DnsTransaction> CreateTransaction(
+      DnsQueryType dns_query_type) {
+    DCHECK_NE(DnsQueryType::UNSPECIFIED, dns_query_type);
     std::unique_ptr<DnsTransaction> trans =
         client_->GetTransactionFactory()->CreateTransaction(
-            key_.hostname,
-            family == ADDRESS_FAMILY_IPV6 ? dns_protocol::kTypeAAAA
-                                          : dns_protocol::kTypeA,
+            key_.hostname, DnsQueryTypeToQtype(dns_query_type),
             base::BindOnce(&DnsTask::OnTransactionComplete,
                            base::Unretained(this), tick_clock_->NowTicks()),
             net_log_);
@@ -1137,13 +1130,15 @@
     }
 
     if (transaction->GetType() == dns_protocol::kTypeA) {
-      DCHECK_EQ(transaction_a_.get(), transaction);
+      DCHECK_EQ(transaction1_.get(), transaction);
       // Place IPv4 addresses after IPv6.
       addr_list_.insert(addr_list_.end(), addr_list.begin(), addr_list.end());
-    } else {
-      DCHECK_EQ(transaction_aaaa_.get(), transaction);
+    } else if (transaction->GetType() == dns_protocol::kTypeAAAA) {
       // Place IPv6 addresses before IPv4.
       addr_list_.insert(addr_list_.begin(), addr_list.begin(), addr_list.end());
+    } else {
+      // TODO(crbug.com/846423): Add result parsing for non-address types.
+      NOTIMPLEMENTED();
     }
 
     // If requested via HOST_RESOLVER_CANONNAME, store the canonical name from
@@ -1237,8 +1232,8 @@
   Delegate* delegate_;
   const NetLogWithSource net_log_;
 
-  std::unique_ptr<DnsTransaction> transaction_a_;
-  std::unique_ptr<DnsTransaction> transaction_aaaa_;
+  std::unique_ptr<DnsTransaction> transaction1_;
+  std::unique_ptr<DnsTransaction> transaction2_;
 
   unsigned num_completed_transactions_;
 
@@ -1756,18 +1751,12 @@
     DCHECK_EQ(0, key_.host_resolver_flags &
                      ~HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6);
 
-    std::vector<HostResolver::DnsQueryType> query_types;
-    switch (key_.address_family) {
-      case ADDRESS_FAMILY_UNSPECIFIED:
-        query_types.push_back(HostResolver::DnsQueryType::A);
-        query_types.push_back(HostResolver::DnsQueryType::AAAA);
-        break;
-      case ADDRESS_FAMILY_IPV4:
-        query_types.push_back(HostResolver::DnsQueryType::A);
-        break;
-      case ADDRESS_FAMILY_IPV6:
-        query_types.push_back(HostResolver::DnsQueryType::AAAA);
-        break;
+    std::vector<DnsQueryType> query_types;
+    if (key_.dns_query_type == DnsQueryType::UNSPECIFIED) {
+      query_types.push_back(DnsQueryType::A);
+      query_types.push_back(DnsQueryType::AAAA);
+    } else {
+      query_types.push_back(key_.dns_query_type);
     }
 
     mdns_task_ = std::make_unique<HostResolverMdnsTask>(
@@ -1814,19 +1803,22 @@
       if (had_non_speculative_request_) {
         category = RESOLVE_SUCCESS;
         UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveSuccessTime", duration);
-        switch (key_.address_family) {
-          case ADDRESS_FAMILY_IPV4:
+        switch (key_.dns_query_type) {
+          case DnsQueryType::A:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveSuccessTime.IPV4",
                                          duration);
             break;
-          case ADDRESS_FAMILY_IPV6:
+          case DnsQueryType::AAAA:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveSuccessTime.IPV6",
                                          duration);
             break;
-          case ADDRESS_FAMILY_UNSPECIFIED:
+          case DnsQueryType::UNSPECIFIED:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveSuccessTime.UNSPEC",
                                          duration);
             break;
+          default:
+            // No histogram for other query types.
+            break;
         }
       } else {
         category = RESOLVE_SPECULATIVE_SUCCESS;
@@ -1839,19 +1831,22 @@
       if (had_non_speculative_request_) {
         category = RESOLVE_FAIL;
         UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveFailureTime", duration);
-        switch (key_.address_family) {
-          case ADDRESS_FAMILY_IPV4:
+        switch (key_.dns_query_type) {
+          case DnsQueryType::A:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveFailureTime.IPV4",
                                          duration);
             break;
-          case ADDRESS_FAMILY_IPV6:
+          case DnsQueryType::AAAA:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveFailureTime.IPV6",
                                          duration);
             break;
-          case ADDRESS_FAMILY_UNSPECIFIED:
+          case DnsQueryType::UNSPECIFIED:
             UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.ResolveFailureTime.UNSPEC",
                                          duration);
             break;
+          default:
+            // No histogram for other query types.
+            break;
         }
       } else {
         category = RESOLVE_SPECULATIVE_FAIL;
@@ -2483,13 +2478,13 @@
                                    AddressList* addresses) {
   DCHECK(addresses);
   DCHECK(net_error);
-  if (ip_address == nullptr)
+  if (ip_address == nullptr || !IsAddressType(key.dns_query_type))
     return false;
 
   *net_error = OK;
   AddressFamily family = GetAddressFamily(*ip_address);
-  if (key.address_family != ADDRESS_FAMILY_UNSPECIFIED &&
-      key.address_family != family) {
+  if (key.dns_query_type != DnsQueryType::UNSPECIFIED &&
+      key.dns_query_type != AddressFamilyToDnsQueryType(family)) {
     // Don't return IPv6 addresses for IPv4 queries, and vice versa.
     *net_error = ERR_NAME_NOT_RESOLVED;
   } else {
@@ -2547,15 +2542,15 @@
   // flexibility, but lose implicit ordering.
   // We prefer IPv6 because "happy eyeballs" will fall back to IPv4 if
   // necessary.
-  if (key.address_family == ADDRESS_FAMILY_IPV6 ||
-      key.address_family == ADDRESS_FAMILY_UNSPECIFIED) {
+  if (key.dns_query_type == DnsQueryType::AAAA ||
+      key.dns_query_type == DnsQueryType::UNSPECIFIED) {
     auto it = hosts.find(DnsHostsKey(hostname, ADDRESS_FAMILY_IPV6));
     if (it != hosts.end())
       addresses->push_back(IPEndPoint(it->second, host_port));
   }
 
-  if (key.address_family == ADDRESS_FAMILY_IPV4 ||
-      key.address_family == ADDRESS_FAMILY_UNSPECIFIED) {
+  if (key.dns_query_type == DnsQueryType::A ||
+      key.dns_query_type == DnsQueryType::UNSPECIFIED) {
     auto it = hosts.find(DnsHostsKey(hostname, ADDRESS_FAMILY_IPV4));
     if (it != hosts.end())
       addresses->push_back(IPEndPoint(it->second, host_port));
@@ -2567,7 +2562,7 @@
           HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) &&
       IsAllIPv4Loopback(*addresses)) {
     Key new_key(key);
-    new_key.address_family = ADDRESS_FAMILY_UNSPECIFIED;
+    new_key.dns_query_type = DnsQueryType::UNSPECIFIED;
     new_key.host_resolver_flags &=
         ~HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
     return ServeFromHosts(new_key, host_port, addresses);
@@ -2584,20 +2579,23 @@
 
   addresses->clear();
 
-  for (const auto& address : resolved_addresses) {
-    // Include the address if:
-    // - caller didn't specify an address family, or
-    // - caller specifically asked for the address family of this address, or
-    // - this is an IPv6 address and caller specifically asked for IPv4 due
-    //   to lack of detected IPv6 support. (See SystemHostResolverCall for
-    //   rationale).
-    if (key.address_family == ADDRESS_FAMILY_UNSPECIFIED ||
-        key.address_family == address.GetFamily() ||
-        (address.GetFamily() == ADDRESS_FAMILY_IPV6 &&
-         key.address_family == ADDRESS_FAMILY_IPV4 &&
-         (key.host_resolver_flags &
-          HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6))) {
-      addresses->push_back(address);
+  if (IsAddressType(key.dns_query_type)) {
+    for (const auto& address : resolved_addresses) {
+      // Include the address if:
+      // - caller didn't specify an address family, or
+      // - caller specifically asked for the address family of this address, or
+      // - this is an IPv6 address and caller specifically asked for IPv4 due
+      //   to lack of detected IPv6 support. (See SystemHostResolverCall for
+      //   rationale).
+      if (key.dns_query_type == DnsQueryType::UNSPECIFIED ||
+          DnsQueryTypeToAddressFamily(key.dns_query_type) ==
+              address.GetFamily() ||
+          (address.GetFamily() == ADDRESS_FAMILY_IPV6 &&
+           key.dns_query_type == DnsQueryType::A &&
+           (key.host_resolver_flags &
+            HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6))) {
+        addresses->push_back(address);
+      }
     }
   }
 
@@ -2665,22 +2663,21 @@
     const NetLogWithSource& net_log) {
   HostResolverFlags effective_flags = flags | additional_resolver_flags_;
 
-  AddressFamily effective_address_family =
-      DnsQueryTypeToAddressFamily(dns_query_type);
+  DnsQueryType effective_query_type = dns_query_type;
 
-  if (effective_address_family == ADDRESS_FAMILY_UNSPECIFIED &&
+  if (effective_query_type == DnsQueryType::UNSPECIFIED &&
       // When resolving IPv4 literals, there's no need to probe for IPv6.
       // When resolving IPv6 literals, there's no benefit to artificially
       // limiting our resolution based on a probe.  Prior logic ensures
-      // that this query is UNSPECIFIED (see effective_address_family
-      // check above) so the code requesting the resolution should be amenable
-      // to receiving a IPv6 resolution.
+      // that this query is UNSPECIFIED (see effective_query_type check above)
+      // so the code requesting the resolution should be amenable to receiving a
+      // IPv6 resolution.
       !use_local_ipv6_ && ip_address == nullptr && !IsIPv6Reachable(net_log)) {
-    effective_address_family = ADDRESS_FAMILY_IPV4;
+    effective_query_type = DnsQueryType::A;
     effective_flags |= HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
   }
 
-  return Key(hostname, effective_address_family, effective_flags, source);
+  return Key(hostname, effective_query_type, effective_flags, source);
 }
 
 bool HostResolverImpl::IsIPv6Reachable(const NetLogWithSource& net_log) {
diff --git a/net/dns/host_resolver_impl.h b/net/dns/host_resolver_impl.h
index c527504..ff2c0a2 100644
--- a/net/dns/host_resolver_impl.h
+++ b/net/dns/host_resolver_impl.h
@@ -24,6 +24,7 @@
 #include "net/dns/host_cache.h"
 #include "net/dns/host_resolver.h"
 #include "net/dns/host_resolver_proc.h"
+#include "net/dns/public/dns_query_type.h"
 #include "net/url_request/url_request_context.h"
 #include "url/gurl.h"
 
diff --git a/net/dns/host_resolver_impl_unittest.cc b/net/dns/host_resolver_impl_unittest.cc
index 82f17cf..1c65e37d 100644
--- a/net/dns/host_resolver_impl_unittest.cc
+++ b/net/dns/host_resolver_impl_unittest.cc
@@ -36,6 +36,7 @@
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_test_util.h"
+#include "net/dns/dns_util.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/dns/mock_mdns_client.h"
 #include "net/dns/mock_mdns_socket_factory.h"
@@ -725,8 +726,10 @@
 
   const HostCache::Entry* GetCacheEntry(const Request& req) {
     DCHECK(resolver_.get() && resolver_->GetHostCache());
-    const HostCache::Key key(req.info().hostname(), req.info().address_family(),
-                             req.info().host_resolver_flags());
+    const HostCache::Key key(
+        req.info().hostname(),
+        AddressFamilyToDnsQueryType(req.info().address_family()),
+        req.info().host_resolver_flags(), HostResolverSource::ANY);
     return resolver_->GetHostCache()->LookupStale(key, base::TimeTicks(),
                                                   nullptr);
   }
@@ -782,11 +785,11 @@
 
   HostResolver::ResolveHostParameters parameters;
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper v4_response(resolver_->CreateRequest(
       HostPortPair("host", 80), NetLogWithSource(), parameters));
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper v6_response(resolver_->CreateRequest(
       HostPortPair("host", 80), NetLogWithSource(), parameters));
 
@@ -827,14 +830,14 @@
 TEST_F(HostResolverImplTest, LocalhostIPV4IPV6Lookup_ResolveHost) {
   HostResolver::ResolveHostParameters parameters;
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper v6_v4_response(resolver_->CreateRequest(
       HostPortPair("localhost6", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(v6_v4_response.result_error(), IsOk());
   EXPECT_THAT(v6_v4_response.request()->GetAddressResults().value().endpoints(),
               testing::IsEmpty());
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper v6_v6_response(resolver_->CreateRequest(
       HostPortPair("localhost6", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(v6_v6_response.result_error(), IsOk());
@@ -848,14 +851,14 @@
       v6_unsp_response.request()->GetAddressResults().value().endpoints(),
       testing::ElementsAre(CreateExpected("::1", 80)));
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper v4_v4_response(resolver_->CreateRequest(
       HostPortPair("localhost", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(v4_v4_response.result_error(), IsOk());
   EXPECT_THAT(v4_v4_response.request()->GetAddressResults().value().endpoints(),
               testing::ElementsAre(CreateExpected("127.0.0.1", 80)));
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper v4_v6_response(resolver_->CreateRequest(
       HostPortPair("localhost", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(v4_v6_response.result_error(), IsOk());
@@ -2440,10 +2443,10 @@
 // passed in.
 TEST_F(HostResolverImplTest, AddressFamilyWithRawIPs_ResolveHost) {
   HostResolver::ResolveHostParameters v4_parameters;
-  v4_parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  v4_parameters.dns_query_type = DnsQueryType::A;
 
   HostResolver::ResolveHostParameters v6_parameters;
-  v6_parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  v6_parameters.dns_query_type = DnsQueryType::AAAA;
 
   ResolveHostResponseHelper v4_v4_request(resolver_->CreateRequest(
       HostPortPair("127.0.0.1", 80), NetLogWithSource(), v4_parameters));
@@ -3125,7 +3128,7 @@
   EXPECT_CALL(*socket_factory_ptr, OnSendTo(_)).Times(2);
 
   HostResolver::ResolveHostParameters parameters;
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   parameters.source = HostResolverSource::MULTICAST_DNS;
 
   ResolveHostResponseHelper response(resolver_->CreateRequest(
@@ -3150,7 +3153,7 @@
   EXPECT_CALL(*socket_factory_ptr, OnSendTo(_)).Times(2);
 
   HostResolver::ResolveHostParameters parameters;
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   parameters.source = HostResolverSource::MULTICAST_DNS;
 
   ResolveHostResponseHelper response(resolver_->CreateRequest(
@@ -4093,7 +4096,7 @@
   // Requests with specified DNS query type.
   HostResolver::ResolveHostParameters parameters;
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper response_specified_ipv4(resolver_->CreateRequest(
       HostPortPair("nx_ipv4", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(response_specified_ipv4.result_error(), IsOk());
@@ -4103,7 +4106,7 @@
                   .endpoints(),
               testing::ElementsAre(CreateExpected("127.0.0.1", 80)));
 
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper response_specified_ipv6(resolver_->CreateRequest(
       HostPortPair("nx_ipv6", 80), NetLogWithSource(), parameters));
   EXPECT_THAT(response_specified_ipv6.result_error(), IsOk());
@@ -5491,10 +5494,10 @@
   ResolveHostResponseHelper response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), base::nullopt));
   HostResolver::ResolveHostParameters parameters;
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper v4_response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), parameters));
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper v6_response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), parameters));
 
@@ -5520,10 +5523,10 @@
 
   ResolveHostResponseHelper no_wifi_response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), base::nullopt));
-  parameters.dns_query_type = HostResolver::DnsQueryType::A;
+  parameters.dns_query_type = DnsQueryType::A;
   ResolveHostResponseHelper no_wifi_v4_response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), parameters));
-  parameters.dns_query_type = HostResolver::DnsQueryType::AAAA;
+  parameters.dns_query_type = DnsQueryType::AAAA;
   ResolveHostResponseHelper no_wifi_v6_response(resolver_->CreateRequest(
       HostPortPair("h1", 80), NetLogWithSource(), parameters));
 
@@ -5554,7 +5557,8 @@
   EXPECT_THAT(request->Resolve(), IsError(ERR_IO_PENDING));
   EXPECT_THAT(request->WaitForResult(), IsError(ERR_NAME_NOT_RESOLVED));
   EXPECT_THAT(request->NumberOfAddresses(), 0);
-  HostCache::Key key(request->info().hostname(), ADDRESS_FAMILY_UNSPECIFIED, 0);
+  HostCache::Key key(request->info().hostname(), DnsQueryType::UNSPECIFIED, 0,
+                     HostResolverSource::ANY);
   HostCache::EntryStaleness staleness;
   const HostCache::Entry* cache_entry =
       resolver_->GetHostCache()->Lookup(key, base::TimeTicks::Now());
@@ -5567,8 +5571,8 @@
   EXPECT_THAT(request->Resolve(), IsError(ERR_IO_PENDING));
   EXPECT_THAT(request->WaitForResult(), IsError(ERR_NAME_NOT_RESOLVED));
   EXPECT_THAT(request->NumberOfAddresses(), 0);
-  HostCache::Key nxkey(request->info().hostname(), ADDRESS_FAMILY_UNSPECIFIED,
-                       0);
+  HostCache::Key nxkey(request->info().hostname(), DnsQueryType::UNSPECIFIED, 0,
+                       HostResolverSource::ANY);
   cache_entry =
       resolver_->GetHostCache()->Lookup(nxkey, base::TimeTicks::Now());
   EXPECT_TRUE(!!cache_entry);
@@ -5586,7 +5590,8 @@
       HostPortPair("empty", 80), NetLogWithSource(), base::nullopt));
   EXPECT_THAT(no_data_response.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
   EXPECT_FALSE(no_data_response.request()->GetAddressResults());
-  HostCache::Key key("empty", ADDRESS_FAMILY_UNSPECIFIED, 0);
+  HostCache::Key key("empty", DnsQueryType::UNSPECIFIED, 0,
+                     HostResolverSource::ANY);
   HostCache::EntryStaleness staleness;
   const HostCache::Entry* cache_entry =
       resolver_->GetHostCache()->Lookup(key, base::TimeTicks::Now());
@@ -5600,7 +5605,8 @@
   EXPECT_THAT(no_domain_response.result_error(),
               IsError(ERR_NAME_NOT_RESOLVED));
   EXPECT_FALSE(no_domain_response.request()->GetAddressResults());
-  HostCache::Key nxkey("nodomain", ADDRESS_FAMILY_UNSPECIFIED, 0);
+  HostCache::Key nxkey("nodomain", DnsQueryType::UNSPECIFIED, 0,
+                       HostResolverSource::ANY);
   cache_entry =
       resolver_->GetHostCache()->Lookup(nxkey, base::TimeTicks::Now());
   EXPECT_TRUE(!!cache_entry);
@@ -5693,7 +5699,7 @@
   ChangeDnsConfig(CreateValidDnsConfig());
   set_allow_fallback_to_proctask(false);
   HostResolver::ResolveHostParameters params;
-  params.dns_query_type = HostResolver::DnsQueryType::A;
+  params.dns_query_type = DnsQueryType::A;
   params.include_canonical_name = true;
   ResolveHostResponseHelper response(resolver_->CreateRequest(
       HostPortPair("alias", 80), NetLogWithSource(), params));
@@ -6187,7 +6193,7 @@
             HostResolverImpl::MODE_FOR_HISTOGRAM_ASYNC_DNS);
 
   // Test upgradability is detected for async DNS.
-  const static std::vector<const char*> upgradable_servers(
+  static const std::vector<const char*> upgradable_servers(
       {// Google Public DNS
        "8.8.8.8", "8.8.4.4", "2001:4860:4860::8888", "2001:4860:4860::8844",
        // Cloudflare DNS
diff --git a/net/dns/host_resolver_mdns_task.cc b/net/dns/host_resolver_mdns_task.cc
index 4ca031d..5f7d82f 100644
--- a/net/dns/host_resolver_mdns_task.cc
+++ b/net/dns/host_resolver_mdns_task.cc
@@ -18,7 +18,7 @@
 
 class HostResolverMdnsTask::Transaction {
  public:
-  Transaction(HostResolver::DnsQueryType query_type, HostResolverMdnsTask* task)
+  Transaction(DnsQueryType query_type, HostResolverMdnsTask* task)
       : query_type_(query_type), result_(ERR_IO_PENDING), task_(task) {}
 
   void Start() {
@@ -28,20 +28,6 @@
     DCHECK_EQ(ERR_IO_PENDING, result_);
     DCHECK(!async_transaction_);
 
-    uint16_t rrtype;
-    switch (query_type_) {
-      case net::HostResolver::DnsQueryType::A:
-        rrtype = net::dns_protocol::kTypeA;
-        break;
-      case net::HostResolver::DnsQueryType::AAAA:
-        rrtype = net::dns_protocol::kTypeAAAA;
-        break;
-      default:
-        // Type not supported for MDNS.
-        NOTREACHED();
-        return;
-    }
-
     // TODO(crbug.com/846423): Use |allow_cached_response| to set the
     // QUERY_CACHE flag or not.
     int flags = MDnsTransaction::SINGLE_RESULT | MDnsTransaction::QUERY_CACHE |
@@ -50,7 +36,7 @@
     // cancel and prevent invocation of OnComplete.
     std::unique_ptr<MDnsTransaction> inner_transaction =
         task_->mdns_client_->CreateTransaction(
-            rrtype, task_->hostname_, flags,
+            DnsQueryTypeToQtype(query_type_), task_->hostname_, flags,
             base::BindRepeating(&HostResolverMdnsTask::Transaction::OnComplete,
                                 base::Unretained(this)));
 
@@ -97,16 +83,17 @@
 
     if (result_ == net::OK) {
       switch (query_type_) {
-        case net::HostResolver::DnsQueryType::A:
+        case DnsQueryType::A:
           task_->result_addresses_.push_back(
               IPEndPoint(parsed->rdata<net::ARecordRdata>()->address(), 0));
           break;
-        case net::HostResolver::DnsQueryType::AAAA:
+        case DnsQueryType::AAAA:
           task_->result_addresses_.push_back(
               IPEndPoint(parsed->rdata<net::AAAARecordRdata>()->address(), 0));
           break;
         default:
-          NOTREACHED();
+          // TODO(crbug.com/846423): Add result parsing for non-address types.
+          NOTIMPLEMENTED();
       }
     }
 
@@ -116,7 +103,7 @@
     task_->CheckCompletion(!async_transaction_);
   }
 
-  const HostResolver::DnsQueryType query_type_;
+  const DnsQueryType query_type_;
 
   // ERR_IO_PENDING until transaction completes (or is cancelled).
   int result_;
@@ -132,10 +119,10 @@
 HostResolverMdnsTask::HostResolverMdnsTask(
     MDnsClient* mdns_client,
     const std::string& hostname,
-    const std::vector<HostResolver::DnsQueryType>& query_types)
+    const std::vector<DnsQueryType>& query_types)
     : mdns_client_(mdns_client), hostname_(hostname), weak_ptr_factory_(this) {
   DCHECK(!query_types.empty());
-  for (HostResolver::DnsQueryType query_type : query_types) {
+  for (DnsQueryType query_type : query_types) {
     transactions_.emplace_back(query_type, this);
   }
 }
diff --git a/net/dns/host_resolver_mdns_task.h b/net/dns/host_resolver_mdns_task.h
index eebf5ffc..90d3a7c 100644
--- a/net/dns/host_resolver_mdns_task.h
+++ b/net/dns/host_resolver_mdns_task.h
@@ -16,6 +16,7 @@
 #include "net/base/completion_once_callback.h"
 #include "net/dns/host_resolver.h"
 #include "net/dns/mdns_client.h"
+#include "net/dns/public/dns_query_type.h"
 
 namespace net {
 
@@ -25,10 +26,9 @@
 class HostResolverMdnsTask {
  public:
   // |mdns_client| must outlive |this|.
-  HostResolverMdnsTask(
-      MDnsClient* mdns_client,
-      const std::string& hostname,
-      const std::vector<HostResolver::DnsQueryType>& query_types);
+  HostResolverMdnsTask(MDnsClient* mdns_client,
+                       const std::string& hostname,
+                       const std::vector<DnsQueryType>& query_types);
   ~HostResolverMdnsTask();
 
   // Starts the task. |completion_callback| will be called asynchronously with
diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc
index b5382ab1..6a03108 100644
--- a/net/dns/mock_host_resolver.cc
+++ b/net/dns/mock_host_resolver.cc
@@ -269,8 +269,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   next_request_id_++;
   int rv = ResolveFromIPLiteralOrCache(
-      info.host_port_pair(), info.address_family(), info.host_resolver_flags(),
-      HostResolverSource::ANY, info.allow_cached_response(), addresses);
+      info.host_port_pair(), AddressFamilyToDnsQueryType(info.address_family()),
+      info.host_resolver_flags(), HostResolverSource::ANY,
+      info.allow_cached_response(), addresses);
   return rv;
 }
 
@@ -283,9 +284,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   next_request_id_++;
   int rv = ResolveFromIPLiteralOrCache(
-      info.host_port_pair(), info.address_family(), info.host_resolver_flags(),
-      HostResolverSource::ANY, info.allow_cached_response(), addresses,
-      stale_info);
+      info.host_port_pair(), AddressFamilyToDnsQueryType(info.address_family()),
+      info.host_resolver_flags(), HostResolverSource::ANY,
+      info.allow_cached_response(), addresses, stale_info);
   return rv;
 }
 
@@ -346,8 +347,7 @@
   num_resolve_++;
   AddressList addresses;
   int rv = ResolveFromIPLiteralOrCache(
-      request->request_host(),
-      DnsQueryTypeToAddressFamily(request->parameters().dns_query_type),
+      request->request_host(), request->parameters().dns_query_type,
       request->host_resolver_flags(), request->parameters().source,
       request->parameters().allow_cached_response, &addresses);
   if (rv == OK && !request->parameters().is_speculative)
@@ -387,7 +387,7 @@
 
 int MockHostResolverBase::ResolveFromIPLiteralOrCache(
     const HostPortPair& host,
-    AddressFamily requested_address_family,
+    DnsQueryType dns_query_type,
     HostResolverFlags flags,
     HostResolverSource source,
     bool allow_cache,
@@ -396,8 +396,9 @@
   IPAddress ip_address;
   if (ip_address.AssignFromIPLiteral(host.host())) {
     // This matches the behavior HostResolverImpl.
-    if (requested_address_family != ADDRESS_FAMILY_UNSPECIFIED &&
-        requested_address_family != GetAddressFamily(ip_address)) {
+    if (dns_query_type != DnsQueryType::UNSPECIFIED &&
+        dns_query_type !=
+            AddressFamilyToDnsQueryType(GetAddressFamily(ip_address))) {
       return ERR_NAME_NOT_RESOLVED;
     }
 
@@ -408,7 +409,7 @@
   }
   int rv = ERR_DNS_CACHE_MISS;
   if (cache_.get() && allow_cache) {
-    HostCache::Key key(host.host(), requested_address_family, flags, source);
+    HostCache::Key key(host.host(), dns_query_type, flags, source);
     const HostCache::Entry* entry;
     if (stale_info)
       entry = cache_->LookupStale(key, tick_clock_->NowTicks(), stale_info);
@@ -434,7 +435,9 @@
   int rv = rules_map_[source]->Resolve(host.host(), requested_address_family,
                                        flags, &addr, nullptr);
   if (cache_.get()) {
-    HostCache::Key key(host.host(), requested_address_family, flags, source);
+    HostCache::Key key(host.host(),
+                       AddressFamilyToDnsQueryType(requested_address_family),
+                       flags, source);
     // Storing a failure with TTL 0 so that it overwrites previous value.
     base::TimeDelta ttl;
     if (rv == OK)
diff --git a/net/dns/mock_host_resolver.h b/net/dns/mock_host_resolver.h
index 9cabbb4..694a6859 100644
--- a/net/dns/mock_host_resolver.h
+++ b/net/dns/mock_host_resolver.h
@@ -21,6 +21,7 @@
 #include "net/dns/host_resolver.h"
 #include "net/dns/host_resolver_proc.h"
 #include "net/dns/host_resolver_source.h"
+#include "net/dns/public/dns_query_type.h"
 
 namespace base {
 class TickClock;
@@ -176,7 +177,7 @@
   // DNS_CACHE_MISS if failed.
   int ResolveFromIPLiteralOrCache(
       const HostPortPair& host,
-      AddressFamily requested_address_family,
+      DnsQueryType dns_query_type,
       HostResolverFlags flags,
       HostResolverSource source,
       bool allow_cache,
diff --git a/net/dns/public/BUILD.gn b/net/dns/public/BUILD.gn
index 6f4b9a73..afe91da 100644
--- a/net/dns/public/BUILD.gn
+++ b/net/dns/public/BUILD.gn
@@ -9,10 +9,14 @@
   visibility = [
     "//net",
     "//net/dns:dns_client",
+    "//net/dns:host_resolver",
+    "//net/dns:host_resolver_impl",
   ]
 
   sources = [
     "dns_protocol.h",
+    "dns_query_type.cc",
+    "dns_query_type.h",
     "util.cc",
     "util.h",
   ]
diff --git a/net/dns/public/dns_query_type.cc b/net/dns/public/dns_query_type.cc
new file mode 100644
index 0000000..ce8f149
--- /dev/null
+++ b/net/dns/public/dns_query_type.cc
@@ -0,0 +1,17 @@
+// 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 "net/dns/public/dns_query_type.h"
+
+namespace net {
+
+bool IsAddressType(DnsQueryType dns_query_type) {
+  // HostResolver treats UNSPECIFIED as A and/or AAAA depending on IPv4/IPv6
+  // settings, so it is here considered an address type.
+  return dns_query_type == DnsQueryType::UNSPECIFIED ||
+         dns_query_type == DnsQueryType::A ||
+         dns_query_type == DnsQueryType::AAAA;
+}
+
+}  // namespace net
diff --git a/net/dns/public/dns_query_type.h b/net/dns/public/dns_query_type.h
new file mode 100644
index 0000000..87abca31
--- /dev/null
+++ b/net/dns/public/dns_query_type.h
@@ -0,0 +1,29 @@
+// 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 NET_DNS_PUBLIC_DNS_QUERY_TYPE_H_
+#define NET_DNS_PUBLIC_DNS_QUERY_TYPE_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// DNS query type for HostResolver requests.
+// See:
+// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
+//
+// TODO(crbug.com/846423): Add support for non-address types.
+enum class DnsQueryType {
+  UNSPECIFIED,
+  A,
+  AAAA,
+};
+
+// |true| iff |dns_query_type| is an address-resulting type, convertable to and
+// from net::AddressFamily.
+bool NET_EXPORT IsAddressType(DnsQueryType dns_query_type);
+
+}  // namespace net
+
+#endif  // NET_DNS_PUBLIC_DNS_QUERY_TYPE_H_
diff --git a/net/socket/ssl_client_socket_impl.cc b/net/socket/ssl_client_socket_impl.cc
index fe71b688..dc7f6cd0 100644
--- a/net/socket/ssl_client_socket_impl.cc
+++ b/net/socket/ssl_client_socket_impl.cc
@@ -845,7 +845,9 @@
       break;
   }
 
-  if (!base::FeatureList::IsEnabled(features::kEnforceTLS13Downgrade)) {
+  if (!base::FeatureList::IsEnabled(features::kEnforceTLS13Downgrade) ||
+      base::GetFieldTrialParamByFeatureAsBool(features::kEnforceTLS13Downgrade,
+                                              "known_roots_only", false)) {
     SSL_set_ignore_tls13_downgrade(ssl_.get(), 1);
   }
 
@@ -1141,17 +1143,21 @@
       }
     }
 
-    if (!base::FeatureList::IsEnabled(features::kEnforceTLS13Downgrade)) {
+    bool enforce_tls13_downgrade_known_roots_only =
+        base::GetFieldTrialParamByFeatureAsBool(
+            features::kEnforceTLS13Downgrade, "known_roots_only", false);
+    if (!base::FeatureList::IsEnabled(features::kEnforceTLS13Downgrade) ||
+        enforce_tls13_downgrade_known_roots_only) {
       // Record metrics on the TLS 1.3 anti-downgrade mechanism. This is only
       // recorded when enforcement is disabled. (When enforcement is enabled,
       // the connection will fail with ERR_TLS13_DOWNGRADE_DETECTED.) See
       // https://crbug.com/boringssl/226.
       //
       // Record metrics for both servers overall and the TLS 1.3 experiment
-      // set. These metrics are only useful on TLS 1.3 servers, so the latter is
-      // more precise, but there is a large enough TLS 1.3 deployment that the
-      // overall numbers may be more robust. In particular, the DowngradeType
-      // metrics do not need to be filtered.
+      // set. These metrics are only useful on TLS 1.3 servers, so the latter
+      // is more precise, but there is a large enough TLS 1.3 deployment that
+      // the overall numbers may be more robust. In particular, the
+      // DowngradeType metrics do not need to be filtered.
       bool is_downgrade = !!SSL_is_tls13_downgrade(ssl_.get());
       UMA_HISTOGRAM_BOOLEAN("Net.SSLTLS13Downgrade", is_downgrade);
       bool is_tls13_experiment_host =
@@ -1165,7 +1171,8 @@
         // Record whether connections which hit the downgrade used known vs
         // unknown roots and which key exchange type.
 
-        // This enum is persisted into histograms. Values may not be renumbered.
+        // This enum is persisted into histograms. Values may not be
+        // renumbered.
         enum class DowngradeType {
           kKnownRootRSA = 0,
           kKnownRootECDHE = 1,
@@ -1190,6 +1197,14 @@
                                     type);
         }
       }
+
+      if (enforce_tls13_downgrade_known_roots_only &&
+          server_cert_verify_result_.is_issued_by_known_root) {
+        // Exit DoHandshakeLoop and return the result to the caller to
+        // Connect.
+        DCHECK_EQ(STATE_NONE, next_handshake_state_);
+        return ERR_TLS13_DOWNGRADE_DETECTED;
+      }
     }
   }
 
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 7f06dd4..bb68f2e4 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -4846,6 +4846,64 @@
             SSLConnectionStatusToVersion(info.connection_status));
 }
 
+// Test downgrade enforcement enforces known roots when configured to.
+TEST_F(SSLClientSocketTest, TLS13DowngradeEnforcedKnownKnown) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      features::kEnforceTLS13Downgrade, {{"known_roots_only", "true"}});
+
+  SpawnedTestServer::SSLOptions ssl_options;
+  ssl_options.simulate_tls13_downgrade = true;
+  ssl_options.tls_max_version =
+      SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_2;
+  ASSERT_TRUE(StartTestServer(ssl_options));
+
+  scoped_refptr<X509Certificate> server_cert =
+      spawned_test_server()->GetCertificate();
+
+  // Certificate is trusted and chains to a public root.
+  CertVerifyResult verify_result;
+  verify_result.is_issued_by_known_root = true;
+  verify_result.verified_cert = server_cert;
+  cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
+
+  SSLConfig config;
+  config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  int rv;
+  ASSERT_TRUE(CreateAndConnectSSLClientSocket(config, &rv));
+  EXPECT_THAT(rv, IsError(ERR_TLS13_DOWNGRADE_DETECTED));
+  EXPECT_FALSE(sock_->IsConnected());
+}
+
+// Test downgrade enforcement ignores unknown roots when configured to.
+TEST_F(SSLClientSocketTest, TLS13DowngradeEnforcedKnownUnknown) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      features::kEnforceTLS13Downgrade, {{"known_roots_only", "true"}});
+
+  SpawnedTestServer::SSLOptions ssl_options;
+  ssl_options.simulate_tls13_downgrade = true;
+  ssl_options.tls_max_version =
+      SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_2;
+  ASSERT_TRUE(StartTestServer(ssl_options));
+
+  scoped_refptr<X509Certificate> server_cert =
+      spawned_test_server()->GetCertificate();
+
+  // Certificate is trusted and chains to a public root.
+  CertVerifyResult verify_result;
+  verify_result.is_issued_by_known_root = false;
+  verify_result.verified_cert = server_cert;
+  cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
+
+  SSLConfig config;
+  config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
+  int rv;
+  ASSERT_TRUE(CreateAndConnectSSLClientSocket(config, &rv));
+  EXPECT_THAT(rv, IsOk());
+  EXPECT_TRUE(sock_->IsConnected());
+}
+
 struct TLS13DowngradeMetricsParams {
   bool downgrade;
   bool known_root;
diff --git a/services/media_session/audio_focus_manager.cc b/services/media_session/audio_focus_manager.cc
index 63a6fa2..f91f0f2 100644
--- a/services/media_session/audio_focus_manager.cc
+++ b/services/media_session/audio_focus_manager.cc
@@ -106,6 +106,15 @@
 
   const std::string& source_name() const { return source_name_; }
 
+  mojom::AudioFocusRequestStatePtr ToAudioFocusRequestState() const {
+    auto request = mojom::AudioFocusRequestState::New();
+    request->session_info = session_info_.Clone();
+    request->audio_focus_type = audio_focus_type_;
+    request->request_id = id_;
+    request->source_name = source_name_;
+    return request;
+  }
+
  private:
   void OnConnectionError() {
     // Since we have multiple pathways that can call |OnConnectionError| we
@@ -158,14 +167,8 @@
 void AudioFocusManager::GetFocusRequests(GetFocusRequestsCallback callback) {
   std::vector<mojom::AudioFocusRequestStatePtr> requests;
 
-  for (const auto& row : audio_focus_stack_) {
-    auto request = mojom::AudioFocusRequestState::New();
-    request->session_info = row->info().Clone();
-    request->audio_focus_type = row->audio_focus_type();
-    request->request_id = row->id();
-    request->source_name = row->source_name();
-    requests.push_back(std::move(request));
-  }
+  for (const auto& row : audio_focus_stack_)
+    requests.push_back(row->ToAudioFocusRequestState());
 
   std::move(callback).Run(std::move(requests));
 }
@@ -190,7 +193,7 @@
 
   if (audio_focus_stack_.back()->id() != id) {
     RemoveFocusEntryIfPresent(id);
-    active_media_controller_.SetMediaSession(GetActiveGainSession());
+    MaybeUpdateActiveSession();
     return;
   }
 
@@ -203,14 +206,14 @@
       observer->OnFocusLost(row->info().Clone());
     });
 
-    active_media_controller_.SetMediaSession(GetActiveGainSession());
+    MaybeUpdateActiveSession();
     return;
   }
 
   if (IsAudioFocusEnforcementEnabled())
     EnforceAudioFocusAbandon(row->audio_focus_type());
 
-  active_media_controller_.SetMediaSession(GetActiveGainSession());
+  MaybeUpdateActiveSession();
 
   // Notify observers that we lost audio focus.
   observers_.ForAllPtrs([&row](mojom::AudioFocusObserver* observer) {
@@ -268,7 +271,7 @@
   row->SetAudioFocusType(type);
   audio_focus_stack_.push_back(std::move(row));
 
-  active_media_controller_.SetMediaSession(GetActiveGainSession());
+  MaybeUpdateActiveSession();
 
   // Notify observers that we were gained audio focus.
   mojom::MediaSessionInfoPtr session_info =
@@ -353,14 +356,29 @@
   }
 }
 
-mojom::MediaSession* AudioFocusManager::GetActiveGainSession() const {
+void AudioFocusManager::MaybeUpdateActiveSession() {
+  StackRow* active = nullptr;
+
   for (auto& row : base::Reversed(audio_focus_stack_)) {
     if (row->audio_focus_type() != mojom::AudioFocusType::kGain)
       continue;
-    return row->session();
+
+    active = row.get();
+    break;
   }
 
-  return nullptr;
+  if (!active_media_controller_.SetMediaSession(active ? active->session()
+                                                       : nullptr)) {
+    return;
+  }
+
+  mojom::AudioFocusRequestStatePtr state =
+      active ? active->ToAudioFocusRequestState() : nullptr;
+
+  // Notify observers that the active media session changed.
+  observers_.ForAllPtrs([&state](mojom::AudioFocusObserver* observer) {
+    observer->OnActiveSessionChanged(state.Clone());
+  });
 }
 
 AudioFocusManager::AudioFocusManager() {
diff --git a/services/media_session/audio_focus_manager.h b/services/media_session/audio_focus_manager.h
index 2218913..17a2184 100644
--- a/services/media_session/audio_focus_manager.h
+++ b/services/media_session/audio_focus_manager.h
@@ -88,8 +88,7 @@
   void AbandonAudioFocusInternal(RequestId);
   void EnforceAudioFocusAbandon(mojom::AudioFocusType);
 
-  // Get the top most media session with the gain audio focus type.
-  mojom::MediaSession* GetActiveGainSession() const;
+  void MaybeUpdateActiveSession();
 
   std::unique_ptr<StackRow> RemoveFocusEntryIfPresent(RequestId id);
 
diff --git a/services/media_session/audio_focus_manager_unittest.cc b/services/media_session/audio_focus_manager_unittest.cc
index 9262093..d9f9a76 100644
--- a/services/media_session/audio_focus_manager_unittest.cc
+++ b/services/media_session/audio_focus_manager_unittest.cc
@@ -985,4 +985,53 @@
   }
 }
 
+TEST_P(AudioFocusManagerTest, ObserverActiveSessionChanged) {
+  test::MockMediaSession media_session_1;
+  test::MockMediaSession media_session_2;
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+
+    RequestAudioFocus(&media_session_1, mojom::AudioFocusType::kGain);
+    EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+              GetState(&media_session_1));
+
+    EXPECT_EQ(media_session_1.GetRequestIdFromClient(),
+              observer->active_session_->request_id);
+  }
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+
+    RequestAudioFocus(&media_session_2, mojom::AudioFocusType::kGainTransient);
+    EXPECT_EQ(mojom::MediaSessionInfo::SessionState::kActive,
+              GetState(&media_session_2));
+
+    EXPECT_NE(
+        test::TestAudioFocusObserver::NotificationType::kActiveSessionChanged,
+        observer->notifications().back());
+    EXPECT_TRUE(observer->active_session_.is_null());
+  }
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    media_session_2.AbandonAudioFocusFromClient();
+
+    EXPECT_NE(
+        test::TestAudioFocusObserver::NotificationType::kActiveSessionChanged,
+        observer->notifications().back());
+    EXPECT_TRUE(observer->active_session_.is_null());
+  }
+
+  {
+    std::unique_ptr<test::TestAudioFocusObserver> observer = CreateObserver();
+    media_session_1.AbandonAudioFocusFromClient();
+
+    EXPECT_EQ(
+        test::TestAudioFocusObserver::NotificationType::kActiveSessionChanged,
+        observer->notifications().back());
+    EXPECT_TRUE(observer->active_session_.is_null());
+  }
+}
+
 }  // namespace media_session
diff --git a/services/media_session/media_controller.cc b/services/media_session/media_controller.cc
index 754f9c91..0482595 100644
--- a/services/media_session/media_controller.cc
+++ b/services/media_session/media_controller.cc
@@ -77,20 +77,24 @@
     session_->NextTrack();
 }
 
-void MediaController::SetMediaSession(mojom::MediaSession* session) {
+bool MediaController::SetMediaSession(mojom::MediaSession* session) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (session == nullptr) {
+  bool changed = session != session_;
+
+  if (changed) {
     session_binding_.Close();
-  } else if (session_ != session) {
-    // Add |this| as an observer for |session|.
-    session_binding_.Close();
-    mojom::MediaSessionObserverPtr observer;
-    session_binding_.Bind(mojo::MakeRequest(&observer));
-    session->AddObserver(std::move(observer));
+
+    if (session) {
+      // Add |this| as an observer for |session|.
+      mojom::MediaSessionObserverPtr observer;
+      session_binding_.Bind(mojo::MakeRequest(&observer));
+      session->AddObserver(std::move(observer));
+    }
   }
 
   session_ = session;
+  return changed;
 }
 
 void MediaController::BindToInterface(mojom::MediaControllerRequest request) {
diff --git a/services/media_session/media_controller.h b/services/media_session/media_controller.h
index 517bb33..4c0d5ef 100644
--- a/services/media_session/media_controller.h
+++ b/services/media_session/media_controller.h
@@ -30,16 +30,19 @@
   void Suspend() override;
   void Resume() override;
   void ToggleSuspendResume() override;
-  void AddObserver(mojom::MediaSessionObserverPtr) override;
+  void AddObserver(mojom::MediaSessionObserverPtr observer) override;
   void PreviousTrack() override;
   void NextTrack() override;
 
   // mojom::MediaSessionObserver overrides.
-  void MediaSessionInfoChanged(mojom::MediaSessionInfoPtr) override;
+  void MediaSessionInfoChanged(
+      mojom::MediaSessionInfoPtr session_info) override;
 
-  void SetMediaSession(mojom::MediaSession*);
+  // Sets the media session that the controller should be bound to. If the
+  // session is already bound to the same session then we will return false.
+  bool SetMediaSession(mojom::MediaSession* session);
 
-  void BindToInterface(mojom::MediaControllerRequest);
+  void BindToInterface(mojom::MediaControllerRequest request);
   void FlushForTesting();
 
  private:
diff --git a/services/media_session/public/cpp/test/audio_focus_test_util.cc b/services/media_session/public/cpp/test/audio_focus_test_util.cc
index 9d356eb..ba349332 100644
--- a/services/media_session/public/cpp/test/audio_focus_test_util.cc
+++ b/services/media_session/public/cpp/test/audio_focus_test_util.cc
@@ -42,6 +42,12 @@
     run_loop_.Quit();
 }
 
+void TestAudioFocusObserver::OnActiveSessionChanged(
+    media_session::mojom::AudioFocusRequestStatePtr session) {
+  active_session_ = std::move(session);
+  notifications_.push_back(NotificationType::kActiveSessionChanged);
+}
+
 void TestAudioFocusObserver::WaitForGainedEvent() {
   if (!focus_gained_session_.is_null())
     return;
diff --git a/services/media_session/public/cpp/test/audio_focus_test_util.h b/services/media_session/public/cpp/test/audio_focus_test_util.h
index 05cd87a..55ec4ce8 100644
--- a/services/media_session/public/cpp/test/audio_focus_test_util.h
+++ b/services/media_session/public/cpp/test/audio_focus_test_util.h
@@ -22,10 +22,11 @@
   TestAudioFocusObserver();
   ~TestAudioFocusObserver() override;
 
-  void OnFocusGained(media_session::mojom::MediaSessionInfoPtr,
-                     media_session::mojom::AudioFocusType) override;
-
-  void OnFocusLost(media_session::mojom::MediaSessionInfoPtr) override;
+  void OnFocusGained(media_session::mojom::MediaSessionInfoPtr session,
+                     media_session::mojom::AudioFocusType type) override;
+  void OnFocusLost(media_session::mojom::MediaSessionInfoPtr session) override;
+  void OnActiveSessionChanged(
+      media_session::mojom::AudioFocusRequestStatePtr session) override;
 
   void WaitForGainedEvent();
   void WaitForLostEvent();
@@ -40,11 +41,13 @@
   // These store the values we received.
   media_session::mojom::MediaSessionInfoPtr focus_gained_session_;
   media_session::mojom::MediaSessionInfoPtr focus_lost_session_;
+  media_session::mojom::AudioFocusRequestStatePtr active_session_;
 
   // These store the order of notifications that were received by the observer.
   enum class NotificationType {
     kFocusGained,
     kFocusLost,
+    kActiveSessionChanged,
   };
   const std::vector<NotificationType>& notifications() const {
     return notifications_;
diff --git a/services/media_session/public/mojom/audio_focus.mojom b/services/media_session/public/mojom/audio_focus.mojom
index 9ad4b27a..2e6d647 100644
--- a/services/media_session/public/mojom/audio_focus.mojom
+++ b/services/media_session/public/mojom/audio_focus.mojom
@@ -38,13 +38,18 @@
 };
 
 // The observer for audio focus events.
-// Next Method ID: 2
 interface AudioFocusObserver {
   // The given |session| gained audio focus with the specified |type|.
-  OnFocusGained@0(MediaSessionInfo session, AudioFocusType type);
+  OnFocusGained(MediaSessionInfo session, AudioFocusType type);
 
   // The given |session| lost audio focus.
-  OnFocusLost@1(MediaSessionInfo session);
+  OnFocusLost(MediaSessionInfo session);
+
+  // The currently active session changed. The active session is the top most
+  // session that is not temporary. This is the session that will be associated
+  // with the MediaController interface. If all the media sessions have ended
+  // then |session| will be null.
+  OnActiveSessionChanged(AudioFocusRequestState? session);
 };
 
 // Controls audio focus for an associated request.
diff --git a/services/network/host_resolver_unittest.cc b/services/network/host_resolver_unittest.cc
index 501a042..a402ccbc 100644
--- a/services/network/host_resolver_unittest.cc
+++ b/services/network/host_resolver_unittest.cc
@@ -19,6 +19,7 @@
 #include "net/base/ip_address.h"
 #include "net/base/net_errors.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/dns/public/dns_query_type.h"
 #include "net/log/net_log.h"
 #include "services/network/host_resolver.h"
 #include "services/network/public/mojom/host_resolver.mojom.h"
@@ -158,7 +159,7 @@
 
   mojom::ResolveHostParametersPtr optional_parameters =
       mojom::ResolveHostParameters::New();
-  optional_parameters->dns_query_type = net::HostResolver::DnsQueryType::AAAA;
+  optional_parameters->dns_query_type = net::DnsQueryType::AAAA;
 
   base::RunLoop run_loop;
   mojom::ResolveHostClientPtr response_client_ptr;
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index c3f2cea..6f2838a 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -59,6 +59,7 @@
 #include "net/dns/host_resolver_impl.h"
 #include "net/dns/host_resolver_source.h"
 #include "net/dns/mock_host_resolver.h"
+#include "net/dns/public/dns_query_type.h"
 #include "net/http/http_auth.h"
 #include "net/http/http_cache.h"
 #include "net/http/http_network_session.h"
@@ -3183,7 +3184,7 @@
   base::RunLoop run_loop;
   mojom::ResolveHostParametersPtr optional_parameters =
       mojom::ResolveHostParameters::New();
-  optional_parameters->dns_query_type = net::HostResolver::DnsQueryType::A;
+  optional_parameters->dns_query_type = net::DnsQueryType::A;
   optional_parameters->source = net::HostResolverSource::DNS;
   mojom::ResolveHostClientPtr response_client_ptr;
   TestResolveHostClient response_client(&response_client_ptr, &run_loop);
diff --git a/services/network/public/cpp/host_resolver.typemap b/services/network/public/cpp/host_resolver.typemap
index 8348c54..8921967 100644
--- a/services/network/public/cpp/host_resolver.typemap
+++ b/services/network/public/cpp/host_resolver.typemap
@@ -5,7 +5,8 @@
 mojom = "//services/network/public/mojom/host_resolver.mojom"
 public_headers = [
   "//net/dns/dns_config_overrides.h",
-  "//net/dns/host_resolver.h",
+  "//net/dns/host_resolver_source.h",
+  "//net/dns/public/dns_query_type.h",
 ]
 traits_headers =
     [ "//services/network/public/cpp/host_resolver_mojom_traits.h" ]
@@ -17,6 +18,6 @@
 ]
 type_mappings = [
   "network.mojom.DnsConfigOverrides=net::DnsConfigOverrides",
-  "network.mojom.ResolveHostParameters.DnsQueryType=net::HostResolver::DnsQueryType",
+  "network.mojom.ResolveHostParameters.DnsQueryType=net::DnsQueryType",
   "network.mojom.ResolveHostParameters.Source=net::HostResolverSource",
 ]
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.cc b/services/network/public/cpp/host_resolver_mojom_traits.cc
index 87051db..d545770 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.cc
+++ b/services/network/public/cpp/host_resolver_mojom_traits.cc
@@ -215,34 +215,32 @@
 }
 
 // static
-ResolveHostParameters::DnsQueryType EnumTraits<
-    ResolveHostParameters::DnsQueryType,
-    net::HostResolver::DnsQueryType>::ToMojom(net::HostResolver::DnsQueryType
-                                                  input) {
+ResolveHostParameters::DnsQueryType
+EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>::ToMojom(
+    net::DnsQueryType input) {
   switch (input) {
-    case net::HostResolver::DnsQueryType::UNSPECIFIED:
+    case net::DnsQueryType::UNSPECIFIED:
       return ResolveHostParameters::DnsQueryType::UNSPECIFIED;
-    case net::HostResolver::DnsQueryType::A:
+    case net::DnsQueryType::A:
       return ResolveHostParameters::DnsQueryType::A;
-    case net::HostResolver::DnsQueryType::AAAA:
+    case net::DnsQueryType::AAAA:
       return ResolveHostParameters::DnsQueryType::AAAA;
   }
 }
 
 // static
-bool EnumTraits<ResolveHostParameters::DnsQueryType,
-                net::HostResolver::DnsQueryType>::
+bool EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>::
     FromMojom(ResolveHostParameters::DnsQueryType input,
-              net::HostResolver::DnsQueryType* output) {
+              net::DnsQueryType* output) {
   switch (input) {
     case ResolveHostParameters::DnsQueryType::UNSPECIFIED:
-      *output = net::HostResolver::DnsQueryType::UNSPECIFIED;
+      *output = net::DnsQueryType::UNSPECIFIED;
       return true;
     case ResolveHostParameters::DnsQueryType::A:
-      *output = net::HostResolver::DnsQueryType::A;
+      *output = net::DnsQueryType::A;
       return true;
     case ResolveHostParameters::DnsQueryType::AAAA:
-      *output = net::HostResolver::DnsQueryType::AAAA;
+      *output = net::DnsQueryType::AAAA;
       return true;
   }
 }
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.h b/services/network/public/cpp/host_resolver_mojom_traits.h
index 33e09d4..3d18769a 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.h
+++ b/services/network/public/cpp/host_resolver_mojom_traits.h
@@ -20,6 +20,7 @@
 #include "net/dns/dns_config_overrides.h"
 #include "net/dns/dns_hosts.h"
 #include "net/dns/host_resolver.h"
+#include "net/dns/public/dns_query_type.h"
 #include "services/network/public/mojom/host_resolver.mojom.h"
 
 namespace mojo {
@@ -72,12 +73,12 @@
 
 template <>
 struct EnumTraits<network::mojom::ResolveHostParameters::DnsQueryType,
-                  net::HostResolver::DnsQueryType> {
+                  net::DnsQueryType> {
   static network::mojom::ResolveHostParameters::DnsQueryType ToMojom(
-      net::HostResolver::DnsQueryType input);
+      net::DnsQueryType input);
   static bool FromMojom(
       network::mojom::ResolveHostParameters::DnsQueryType input,
-      net::HostResolver::DnsQueryType* output);
+      net::DnsQueryType* output);
 };
 
 template <>
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index 06b301b..109ee11 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -54,8 +54,8 @@
       base::ResetAndReturn(&stop_and_flush_callback_)
           .Run(/*metadata=*/std::move(*metadata));
 
-      base::SequencedTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, std::move(tracing_over_callback_));
+      std::move(tracing_over_callback_).Run();
+      // |this| is now destroyed.
     }
   }
 
diff --git a/testing/libfuzzer/fuzzers/generate_javascript_parser_proto.py b/testing/libfuzzer/fuzzers/generate_javascript_parser_proto.py
index 4b82042..a2bac4f 100755
--- a/testing/libfuzzer/fuzzers/generate_javascript_parser_proto.py
+++ b/testing/libfuzzer/fuzzers/generate_javascript_parser_proto.py
@@ -117,7 +117,7 @@
       '\n'
       '// Bound calls to token_to_string to prevent memory usage from growing\n'
       '// too much.\n'
-      'const int kMaxRecursiveDepth = 10;\n'
+      'const int kMaxRecursiveDepth = 9;\n'
       '\n'
       'std::string token_to_string(\n'
       '    const javascript_parser_proto_fuzzer::Token& token, int depth)'
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 9646d69..b95abe6 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2160,6 +2160,30 @@
             ]
         }
     ],
+    "HTMLParsingYieldTime": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_50ms",
+                    "params": {
+                        "limit": "0.05"
+                    },
+                    "enable_features": [
+                        "HTMLParsingYieldTime"
+                    ]
+                }
+            ]
+        }
+    ],
     "HTTPBadPhase3": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index e9cd1634..b462fbf 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -4767,13 +4767,6 @@
 # WebRTC: In "Plan B", remote tracks are unmuted by default, so tests time out
 # waiting for "unmute".
 crbug.com/889487 external/wpt/webrtc/RTCPeerConnection-remote-track-mute.https.html [ Timeout ]
-# WebRTC: In "Unified Plan", remote tracks are correctly muted by default, but
-# they unmute whether or not RTP packets arrive which is incorrect, this causes
-# a timeout in the "checkMute" test because the "unmute" listeners are hooked up
-# after we've already unmuted. The "checkMute" test likely contain bugs that
-# would cause a timeout anyway, but our implementation times out even before
-# that part due to incorrect behavior.
-crbug.com/889487 virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCRtpTransceiver.https.html [ Timeout ]
 
 # Sheriff 2018-04-11
 crbug.com/831796 fast/events/autoscroll-in-textfield.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
index 72f366e..b1036086 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST_5.json
@@ -154228,31 +154228,66 @@
      {}
     ]
    ],
+   "feature-policy/reporting/camera-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/camera-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/document-write-report-only.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/document-write-reporting.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/encrypted-media-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/encrypted-media-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/fullscreen-report-only.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/fullscreen-reporting.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/generic-sensor-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/generic-sensor-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/geolocation-report-only.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "feature-policy/reporting/geolocation-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/geolocation-reporting.https.html.headers": [
     [
      {}
@@ -154263,26 +154298,61 @@
      {}
     ]
    ],
+   "feature-policy/reporting/microphone-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/microphone-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/midi-report-only-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "feature-policy/reporting/midi-report-only.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/midi-reporting.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/payment-report-only.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "feature-policy/reporting/payment-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/payment-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/picture-in-picture-report-only.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/picture-in-picture-reporting.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/sync-xhr-report-only.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/sync-xhr-reporting.html.headers": [
     [
      {}
@@ -154293,16 +154363,31 @@
      {}
     ]
    ],
+   "feature-policy/reporting/usb-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/usb-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/vr-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/vr-reporting.https.html.headers": [
     [
      {}
     ]
    ],
+   "feature-policy/reporting/xr-report-only.https.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/xr-reporting.https.html.headers": [
     [
      {}
@@ -225319,60 +225404,128 @@
      }
     ]
    ],
+   "feature-policy/reporting/camera-report-only.https.html": [
+    [
+     "/feature-policy/reporting/camera-report-only.https.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "feature-policy/reporting/camera-reporting.https.html": [
     [
      "/feature-policy/reporting/camera-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/document-write-report-only.html": [
+    [
+     "/feature-policy/reporting/document-write-report-only.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/document-write-reporting.html": [
     [
      "/feature-policy/reporting/document-write-reporting.html",
      {}
     ]
    ],
+   "feature-policy/reporting/encrypted-media-report-only.https.html": [
+    [
+     "/feature-policy/reporting/encrypted-media-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/encrypted-media-reporting.https.html": [
     [
      "/feature-policy/reporting/encrypted-media-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/fullscreen-report-only.html": [
+    [
+     "/feature-policy/reporting/fullscreen-report-only.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "feature-policy/reporting/fullscreen-reporting.html": [
     [
      "/feature-policy/reporting/fullscreen-reporting.html",
      {}
     ]
    ],
+   "feature-policy/reporting/generic-sensor-report-only.https.html": [
+    [
+     "/feature-policy/reporting/generic-sensor-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/generic-sensor-reporting.https.html": [
     [
      "/feature-policy/reporting/generic-sensor-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/geolocation-report-only.https.html": [
+    [
+     "/feature-policy/reporting/geolocation-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/geolocation-reporting.https.html": [
     [
      "/feature-policy/reporting/geolocation-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/microphone-report-only.https.html": [
+    [
+     "/feature-policy/reporting/microphone-report-only.https.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "feature-policy/reporting/microphone-reporting.https.html": [
     [
      "/feature-policy/reporting/microphone-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/midi-report-only.html": [
+    [
+     "/feature-policy/reporting/midi-report-only.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/midi-reporting.html": [
     [
      "/feature-policy/reporting/midi-reporting.html",
      {}
     ]
    ],
+   "feature-policy/reporting/payment-report-only.https.html": [
+    [
+     "/feature-policy/reporting/payment-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/payment-reporting.https.html": [
     [
      "/feature-policy/reporting/payment-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/picture-in-picture-report-only.html": [
+    [
+     "/feature-policy/reporting/picture-in-picture-report-only.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "feature-policy/reporting/picture-in-picture-reporting.html": [
     [
      "/feature-policy/reporting/picture-in-picture-reporting.html",
@@ -225381,6 +225534,12 @@
      }
     ]
    ],
+   "feature-policy/reporting/sync-xhr-report-only.html": [
+    [
+     "/feature-policy/reporting/sync-xhr-report-only.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/sync-xhr-reporting.html": [
     [
      "/feature-policy/reporting/sync-xhr-reporting.html",
@@ -225393,18 +225552,38 @@
      {}
     ]
    ],
+   "feature-policy/reporting/usb-report-only.https.html": [
+    [
+     "/feature-policy/reporting/usb-report-only.https.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "feature-policy/reporting/usb-reporting.https.html": [
     [
      "/feature-policy/reporting/usb-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/vr-report-only.https.html": [
+    [
+     "/feature-policy/reporting/vr-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/vr-reporting.https.html": [
     [
      "/feature-policy/reporting/vr-reporting.https.html",
      {}
     ]
    ],
+   "feature-policy/reporting/xr-report-only.https.html": [
+    [
+     "/feature-policy/reporting/xr-report-only.https.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/xr-reporting.https.html": [
     [
      "/feature-policy/reporting/xr-reporting.https.html",
@@ -341213,7 +341392,7 @@
    "testharness"
   ],
   "css/css-properties-values-api/var-reference-registered-properties-cycles.html": [
-   "58d6c846ae3a4a1c62f239786dfe8827eb921d05",
+   "65d11697355c17fca39b4e9fa0dcf0db404e22eb",
    "testharness"
   ],
   "css/css-properties-values-api/var-reference-registered-properties.html": [
@@ -381308,48 +381487,100 @@
    "1759381fdc4141302f1b95868550ead76d9f5ca7",
    "support"
   ],
+  "feature-policy/reporting/camera-report-only.https.html": [
+   "26488687e6fc75247356d716fa32134dc695fadf",
+   "testharness"
+  ],
+  "feature-policy/reporting/camera-report-only.https.html.headers": [
+   "46b84811f55556b8dc4cbc87c174e1175b73e47b",
+   "support"
+  ],
   "feature-policy/reporting/camera-reporting.https.html": [
-   "14b2ed1d37c6d79c9e011a35b9fece0cddc0ac76",
+   "1d08700d3c834692d99dee98fc3f6ebfc0245277",
    "testharness"
   ],
   "feature-policy/reporting/camera-reporting.https.html.headers": [
    "2adc5e237fcec874767ff8f5ab38e3456c02cbf0",
    "support"
   ],
+  "feature-policy/reporting/document-write-report-only.html": [
+   "ab0bb827166327228e825f9f9c5a0447666d17e1",
+   "testharness"
+  ],
+  "feature-policy/reporting/document-write-report-only.html.headers": [
+   "63e43f1d719302760ccdd902439c32913bda375c",
+   "support"
+  ],
   "feature-policy/reporting/document-write-reporting.html": [
-   "cb08b8d72d1851ca02c51abf5713519d52f1ecf4",
+   "d113f2e4a4cc95189044fb46fff8c487f2e8fb68",
    "testharness"
   ],
   "feature-policy/reporting/document-write-reporting.html.headers": [
    "57102d5ee7c072e433fa44c0c13521573b32f469",
    "support"
   ],
+  "feature-policy/reporting/encrypted-media-report-only.https.html": [
+   "20e44b2fd5db2ac6f54b67c909f6d31099edfaa3",
+   "testharness"
+  ],
+  "feature-policy/reporting/encrypted-media-report-only.https.html.headers": [
+   "a2c8fbb44259448cadc9290aad6853051a28aba6",
+   "support"
+  ],
   "feature-policy/reporting/encrypted-media-reporting.https.html": [
-   "d309d5390f1ec5f6f3edd00327e9a36b54926499",
+   "c3b63936b3a8310f873c748947f0fc0633928a9b",
    "testharness"
   ],
   "feature-policy/reporting/encrypted-media-reporting.https.html.headers": [
    "73753a2e41d10b0a0398831350b1f17e586ab847",
    "support"
   ],
+  "feature-policy/reporting/fullscreen-report-only.html": [
+   "a6b3d5adeed5654942aba43dc65d627fa7c35ae0",
+   "testharness"
+  ],
+  "feature-policy/reporting/fullscreen-report-only.html.headers": [
+   "33defa88f6ef9842e4b388dc853ff88c2947e81d",
+   "support"
+  ],
   "feature-policy/reporting/fullscreen-reporting.html": [
-   "83d97c91fab60e593cf3bf0cceff4db80552ef02",
+   "9a7fb8785594a09e862539e671eb6562043263cb",
    "testharness"
   ],
   "feature-policy/reporting/fullscreen-reporting.html.headers": [
    "d35e48ba40dc65a3b043a3e41a11332c42bfdba9",
    "support"
   ],
+  "feature-policy/reporting/generic-sensor-report-only.https.html": [
+   "deb6adeb73679e3e456787c23ee1a9acad1b8901",
+   "testharness"
+  ],
+  "feature-policy/reporting/generic-sensor-report-only.https.html.headers": [
+   "26605eb0fa6c46f9991071f32f9ca35adf00acb2",
+   "support"
+  ],
   "feature-policy/reporting/generic-sensor-reporting.https.html": [
-   "c60e3e81a1dddbdc000d799c19719c48ad55376c",
+   "517c7f6872146a66b02982c0d9e895b60f6d89af",
    "testharness"
   ],
   "feature-policy/reporting/generic-sensor-reporting.https.html.headers": [
    "80cc02753044a3730695bce65bc2b4c22d7a8a6b",
    "support"
   ],
+  "feature-policy/reporting/geolocation-report-only.https-expected.txt": [
+   "93220611ee3618ea05ef73671e21cc39227bf024",
+   "support"
+  ],
+  "feature-policy/reporting/geolocation-report-only.https.html": [
+   "cf2a75b766c3e60b988a0e6e70f65d13b2b79fcf",
+   "testharness"
+  ],
+  "feature-policy/reporting/geolocation-report-only.https.html.headers": [
+   "fc9859000f566801a83d89f1b1371017847c9eb6",
+   "support"
+  ],
   "feature-policy/reporting/geolocation-reporting.https.html": [
-   "22e258563b799c7d48db2452f15eb6124d1f2d0e",
+   "ce069023228bdd8294026e1663325b75b3a97265",
    "testharness"
   ],
   "feature-policy/reporting/geolocation-reporting.https.html.headers": [
@@ -381360,40 +381591,88 @@
    "c059b96d97fc3701ce4325165b79948f69189135",
    "support"
   ],
+  "feature-policy/reporting/microphone-report-only.https.html": [
+   "2d7b4d960d8b5f1c16be272f39c22947370a2611",
+   "testharness"
+  ],
+  "feature-policy/reporting/microphone-report-only.https.html.headers": [
+   "7673d05b9485d7d2aba2e1d952b65797fdbb0827",
+   "support"
+  ],
   "feature-policy/reporting/microphone-reporting.https.html": [
-   "7347a2332bc8184b2437a965e95b54ff5fcac22f",
+   "4a0e0b5e98506667b07f02368f962418da818e09",
    "testharness"
   ],
   "feature-policy/reporting/microphone-reporting.https.html.headers": [
    "a86e0a077851a84f704e2aa4df1d526ecd9a55b2",
    "support"
   ],
+  "feature-policy/reporting/midi-report-only-expected.txt": [
+   "bba8e2aa4e6bcfcd26d918c967e6b0f4d14078ae",
+   "support"
+  ],
+  "feature-policy/reporting/midi-report-only.html": [
+   "e466ce0dc412e14296755c2608dc9b4ac1bdc4f6",
+   "testharness"
+  ],
+  "feature-policy/reporting/midi-report-only.html.headers": [
+   "3c6a2d4fbb320425b3850eaea2eceaf038b0ba62",
+   "support"
+  ],
   "feature-policy/reporting/midi-reporting.html": [
-   "8303b7adce4de654e3a846bbbe1cba035ac9a284",
+   "fc6d8b1abac7b7a280c32aabea78cbfe51a405b1",
    "testharness"
   ],
   "feature-policy/reporting/midi-reporting.html.headers": [
    "0e145978a014f08fb5faff42750e9338da0f9ede",
    "support"
   ],
+  "feature-policy/reporting/payment-report-only.https-expected.txt": [
+   "19f3d28fba84148a0470a45d5d4feaa359badb3a",
+   "support"
+  ],
+  "feature-policy/reporting/payment-report-only.https.html": [
+   "6a7678b51e759eef7c90e924a596bbf5c1364e15",
+   "testharness"
+  ],
+  "feature-policy/reporting/payment-report-only.https.html.headers": [
+   "6411478e4ca37bd2eab26a35b5ae0e6e39ddf066",
+   "support"
+  ],
   "feature-policy/reporting/payment-reporting.https.html": [
-   "03eaebea58fb603f5121f6836c85c2c6773c3a5d",
+   "2c1189d5b46d614fc94038ca282b494613601377",
    "testharness"
   ],
   "feature-policy/reporting/payment-reporting.https.html.headers": [
    "a2836778bc5389fdb65e9f6d5f83c7967200866f",
    "support"
   ],
+  "feature-policy/reporting/picture-in-picture-report-only.html": [
+   "157670f2cab802bf3f07343eab6649152b08c126",
+   "testharness"
+  ],
+  "feature-policy/reporting/picture-in-picture-report-only.html.headers": [
+   "0df90a3e94a3f4997b97dccac3b18aa2830045e8",
+   "support"
+  ],
   "feature-policy/reporting/picture-in-picture-reporting.html": [
-   "e3cbf1036871a40163a67aab22930fb7dcde8338",
+   "f15f47c549d9f03c6b2c49d4e00112900491174d",
    "testharness"
   ],
   "feature-policy/reporting/picture-in-picture-reporting.html.headers": [
    "1759381fdc4141302f1b95868550ead76d9f5ca7",
    "support"
   ],
+  "feature-policy/reporting/sync-xhr-report-only.html": [
+   "f841f63d6ed3503e7370d11a5305cd95588b1ff9",
+   "testharness"
+  ],
+  "feature-policy/reporting/sync-xhr-report-only.html.headers": [
+   "79a82cfb6351efe60fca8945e11d8ec58719dc8d",
+   "support"
+  ],
   "feature-policy/reporting/sync-xhr-reporting.html": [
-   "2c76390847be29c5bb2f3ec8605d1cb746c33e00",
+   "f16e335dd482b30613472c857d2eb24b584b600e",
    "testharness"
   ],
   "feature-policy/reporting/sync-xhr-reporting.html.headers": [
@@ -381408,24 +381687,48 @@
    "db2dcbc1929b9e1264855e9b80f77dfbda5d4f38",
    "support"
   ],
+  "feature-policy/reporting/usb-report-only.https.html": [
+   "e44c6c528fd4fc9b1d7d96ea65d68a922748bc47",
+   "testharness"
+  ],
+  "feature-policy/reporting/usb-report-only.https.html.headers": [
+   "bd2e3c6fe5085b0faa97c2439920d39175497acb",
+   "support"
+  ],
   "feature-policy/reporting/usb-reporting.https.html": [
-   "f90c602e449bd00e1d773edbce01e4fab63341e2",
+   "bc4bffdd98a81d96d3337bd0fee905abec14feda",
    "testharness"
   ],
   "feature-policy/reporting/usb-reporting.https.html.headers": [
    "4fd1e269362c43d282ca8e3c5c35a5d648f0666b",
    "support"
   ],
+  "feature-policy/reporting/vr-report-only.https.html": [
+   "91016d388532c5528f02e31b308178573191e2ef",
+   "testharness"
+  ],
+  "feature-policy/reporting/vr-report-only.https.html.headers": [
+   "b54cad2af941d68b2d395e7d0b9f0af3931bbd01",
+   "support"
+  ],
   "feature-policy/reporting/vr-reporting.https.html": [
-   "12cae052a167b47d020dcb8ec7887e5578ce48c5",
+   "927851989718724d2ea089112d156ab1db9bd7c7",
    "testharness"
   ],
   "feature-policy/reporting/vr-reporting.https.html.headers": [
    "d021af75636de273c868412cd98dfff50576151d",
    "support"
   ],
+  "feature-policy/reporting/xr-report-only.https.html": [
+   "5d4fb062c1ea2b49fff4068f33926f5afdf8c446",
+   "testharness"
+  ],
+  "feature-policy/reporting/xr-report-only.https.html.headers": [
+   "b54cad2af941d68b2d395e7d0b9f0af3931bbd01",
+   "support"
+  ],
   "feature-policy/reporting/xr-reporting.https.html": [
-   "a7a122237bb0bdef6d35954697c5a80dbebb8b0f",
+   "0c793d5b4890d4858018d9f9659adfc717728381",
    "testharness"
   ],
   "feature-policy/reporting/xr-reporting.https.html.headers": [
@@ -383189,7 +383492,7 @@
    "support"
   ],
   "fetch/sec-metadata/resources/record-header.py": [
-   "4c30d1e52ac8bfb24c890f790df154ea17947043",
+   "b81e93ec3c09a4bbfae7190e39508b31b68edfd7",
    "support"
   ],
   "fetch/sec-metadata/resources/sharedWorker.js": [
@@ -438693,11 +438996,11 @@
    "support"
   ],
   "webrtc/RTCRtpTransceiver.https-expected.txt": [
-   "4807af0735ee68ed4971525fe6d37079b3ae8a50",
+   "3941d3d372d904c093f42f4f85786b63057f5962",
    "support"
   ],
   "webrtc/RTCRtpTransceiver.https.html": [
-   "7d16deaa8c7633928b39e1ed638f26e820e1e8d8",
+   "1614a11c1a1d6f6de8660117a47753d2c4154726",
    "testharness"
   ],
   "webrtc/RTCSctpTransport-constructor-expected.txt": [
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001-ref.html
new file mode 100644
index 0000000..31a9ef3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001-ref.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS container Layout Test Reference</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+  .container {
+    margin: 10px;
+    background: grey;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .container > div {
+    background: cyan;
+    width: 100%;
+    height: 100%;
+  }
+
+  .directionRTL {
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001.html
new file mode 100644
index 0000000..1b3c6ee
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-001.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid container with scrollbars</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#grid-model">
+<link rel="match" href="grid-container-scrollbar-001-ref.html">
+<meta name="assert" content="This test verifes that scrollbars are properly painted on grid containers, and are shown in the expected position depending on the direction.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  .grid {
+    margin: 10px;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .grid > div {
+    background: cyan;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001-ref.html
new file mode 100644
index 0000000..512fb8a8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001-ref.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS container Layout Test Reference</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+  .container {
+    margin: 10px;
+    background: grey;
+    writing-mode: vertical-lr;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .container > div {
+    background: cyan;
+    width: 100%;
+    height: 100%;
+  }
+
+  .directionRTL {
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001.html
new file mode 100644
index 0000000..ecdc7b7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-lr-001.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid container with scrollbars</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#grid-model">
+<link rel="match" href="grid-container-scrollbar-vertical-lr-001-ref.html">
+<meta name="assert" content="This test verifes that scrollbars are properly painted on grid containers, and are shown in the expected position depending on the direction.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  .grid {
+    margin: 10px;
+    writing-mode: vertical-lr;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .grid > div {
+    background: cyan;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001-ref.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001-ref.html
new file mode 100644
index 0000000..d8eeedfa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001-ref.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS container Layout Test Reference</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<style>
+  .container {
+    margin: 10px;
+    background: grey;
+    writing-mode: vertical-rl;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .container > div {
+    background: cyan;
+    width: 100%;
+    height: 100%;
+  }
+
+  .directionRTL {
+    direction: rtl;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL container scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL container fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001.html
new file mode 100644
index 0000000..f2ab39ff5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-grid/grid-model/grid-container-scrollbar-vertical-rl-001.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Grid container with scrollbars</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#grid-model">
+<link rel="match" href="grid-container-scrollbar-vertical-rl-001-ref.html">
+<meta name="assert" content="This test verifes that scrollbars are properly painted on grid containers, and are shown in the expected position depending on the direction.">
+<link href="support/grid.css" rel="stylesheet">
+<style>
+  .grid {
+    margin: 10px;
+    writing-mode: vertical-rl;
+  }
+
+  .scrollX {
+    overflow-x: scroll;
+  }
+
+  .scrollY {
+    overflow-y: scroll;
+  }
+
+  .fixedSize {
+    width: 200px;
+    height: 50px;
+  }
+
+  .grid > div {
+    background: cyan;
+  }
+</style>
+
+<p>The test passes if it has the same output than the reference.</p>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: ltr;</h2>
+
+  <div class="grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
+
+<div style="float: left; width: 350px;">
+
+  <h2>direction: rtl;</h2>
+
+  <div class="directionRTL grid scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid scrollX scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollY">
+    <div>item</div>
+  </div>
+
+  <div class="directionRTL grid fixedSize scrollX scrollY">
+    <div>item</div>
+  </div>
+
+</div>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/fetch/sec-metadata/resources/record-header.py b/third_party/WebKit/LayoutTests/external/wpt/fetch/sec-metadata/resources/record-header.py
index 4c30d1e..b81e93e 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/fetch/sec-metadata/resources/record-header.py
+++ b/third_party/WebKit/LayoutTests/external/wpt/fetch/sec-metadata/resources/record-header.py
@@ -3,10 +3,7 @@
 import hashlib
 import time
 
-resourcePath = os.getcwd() + "/fetch/sec-metadata/resources/"
-
 def main(request, response):
-
   ## Get the query parameter (key) from URL ##
   ## Tests will record POST requests (CSP Report) and GET (rest) ##
   if request.GET:
@@ -57,7 +54,8 @@
     ## Return a valid SharedWorker ##
     if key.startswith("sharedworker"):
       response.headers.set("Content-Type", "application/javascript")
-      file = open(resourcePath + "sharedWorker.js", "r")
+      file = open(os.path.join(request.doc_root, "fetch", "sec-metadata",
+                               "resources", "sharedWorker.js"), "r")
       shared_worker = file.read()
       file.close()
       return shared_worker
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
index 6f03fd8..e8b8e9a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/navigation-redirect.https.html
@@ -97,7 +97,7 @@
   Object.keys(clients).forEach(key => {
     const info = clients[key];
     if (info.found) {
-      assert_true(expected_final_client_tag,
+      assert_true(!!expected_final_client_tag,
                   `${worker_name} client tag exists`);
       assert_equals(key, expected_final_client_tag,
                     `${worker_name} client tag matches`);
@@ -120,10 +120,10 @@
     assert_equals(typeof(actual_id), 'string',
                   `resultingClientId for ${url} request to ${worker}`);
     if (expected_id) {
-      assert_equals(requestInfos[0], expected_id,
+      assert_equals(actual_id, expected_id,
                     `resultingClientId for ${url} request to ${worker}`);
     } else {
-      actual_ids[key] = actual_id;
+      actual_ids[tag] = actual_id;
     }
   }
 }
@@ -489,7 +489,7 @@
     url1,
     url2,
     [[{url: url1, resultingClientIdTag: 'a'}], [], []],
-    'x',
+    null,
     'SW-fallbacked redirect to other-origin out-scope.');
 
 url1 = SCOPE1 + 'url=' + encodeURIComponent(OTHER_ORIGIN_SCOPE);
@@ -530,8 +530,8 @@
 redirect_test(
     url1,
     url2,
-    [[{url: url1, resultingClientIdTag: 'a'}], [], []],
-    null,
+    [[{url: url1, resultingClientIdTag: 'x'}], [], []],
+    'x',
     'SW-generated redirect to same-origin out-scope.');
 
 url1 = SCOPE1 + 'sw=gen&url=' + encodeURIComponent(OUT_SCOPE) + '#ref';
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
index 0c5bc3bd..ddcc2cf 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/service-worker/resources/redirect-worker.js
@@ -114,7 +114,7 @@
       return;
     }
 
-    event.respondWith(waitUntilPromise.then(function() {
+    event.respondWith(waitUntilPromise.then(async () => {
       if (params['sw'] == 'gen') {
         return Response.redirect(params['url']);
       } else if (params['sw'] == 'fetch') {
@@ -126,18 +126,13 @@
       } else if (params['sw'] == 'manual') {
         return fetch(new Request(event.request.url, {redirect: 'manual'}));
       } else if (params['sw'] == 'manualThroughCache') {
-        var url = event.request.url;
-        var cache;
-        return caches.delete(url)
-          .then(function() { return self.caches.open(url); })
-          .then(function(c) {
-            cache = c;
-            return fetch(new Request(url, {redirect: 'manual'}));
-          })
-          .then(function(res) { return cache.put(event.request, res); })
-          .then(function() { return cache.match(url); });
+        const url = event.request.url;
+        await caches.delete(url)
+        const cache = await self.caches.open(url);
+        const response = await fetch(new Request(url, {redirect: 'manual'}));
+        await cache.put(event.request, response);
+        return cache.match(url);
       }
-
       // unexpected... trigger an interception failure
     }));
   });
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
index 4807af0..3941d3d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
@@ -1,37 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = Cannot read property 'receiver' of undefined
-FAIL checkAddTransceiverNoTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverWithTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverWithAddTrack assert_equals: expected "[{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"audio\"}},sender:{track:{}},stopped:false},{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"video\"}},sender:{track:{}},stopped:false}]" but got "[]"
-FAIL checkAddTransceiverWithDirection promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverWithSetRemoteOfferSending promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverWithSetRemoteOfferNoSend promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverBadKind assert_true: addTransceiver("foo") throws a TypeError expected true got false
-FAIL checkNoMidOffer assert_equals: expected "[{currentDirection:null,direction:\"recvonly\",receiver:{track:{kind:\"audio\"}},sender:{track:null},stopped:false}]" but got "[]"
-FAIL checkSetDirection promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkCurrentDirection assert_equals: expected "[{currentDirection:null}]" but got "[]"
-FAIL checkSendrecvWithNoSendTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkSendrecvWithTracklessStream promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverNoTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverWithTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverThenReplaceTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTransceiverThenAddTrackPairs promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkAddTrackPairs promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkReplaceTrackNullDoesntPreventPairing promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkRemoveAndReadd assert_equals: expected "[{direction:\"recvonly\",sender:{track:null}},{direction:\"sendrecv\",sender:{track:{}}}]" but got "[]"
-FAIL checkAddTrackExistingTransceiverThenRemove promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument."
-FAIL checkRemoveTrackNegotiation promise_test: Unhandled rejection with value: object "TypeError: Cannot set property 'direction' of undefined"
-FAIL checkMute promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'receiver' of undefined"
-FAIL checkStop promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined"
-FAIL checkStopAfterCreateOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined"
-FAIL checkStopAfterSetLocalOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined"
-FAIL checkStopAfterSetRemoteOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined"
-FAIL checkStopAfterCreateAnswer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined"
-FAIL checkStopAfterSetLocalAnswer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'receiver' of undefined"
-FAIL checkStopAfterClose assert_equals: Stopping a transceiver on a closed PC should throw. throws InvalidStateError expected "InvalidStateError" but got "TypeError"
-FAIL checkLocalRollback assert_equals: expected "[{currentDirection:null,direction:\"sendrecv\",receiver:{track:{kind:\"audio\"}},sender:{track:{}},stopped:false}]" but got "[]"
-FAIL checkRollbackAndSetRemoteOfferWithDifferentType promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType."
-FAIL checkRemoteRollback promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType."
-FAIL checkMsectionReuse promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'mid' of undefined"
+FAIL RTCRtpTransceiver Uncaught ReferenceError: checkMsidNoTrackId is not defined
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https.html
index 7d16dea..1614a11 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCRtpTransceiver.https.html
@@ -2129,6 +2129,7 @@
   checkAddTransceiverWithTrack,
   checkAddTransceiverWithAddTrack,
   checkAddTransceiverWithDirection,
+  checkMsidNoTrackId,
   checkAddTransceiverWithSetRemoteOfferSending,
   checkAddTransceiverWithSetRemoteOfferNoSend,
   checkAddTransceiverBadKind,
diff --git a/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt b/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
deleted file mode 100644
index 7a9017a..0000000
--- a/third_party/WebKit/LayoutTests/virtual/webrtc-wpt-unified-plan/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-This is a testharness.js-based test.
-FAIL checkAddTransceiverNoTrack assert_equals: expected "[{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"audio\",muted:true,readyState:\"live\"}},sender:{track:null},stopped:false},{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"video\",muted:true,readyState:\"live\"}},sender:{track:null},stopped:false}]" but got "[{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"audio\",muted:false,readyState:\"live\"}},sender:{track:null},stopped:false},{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"video\",muted:false,readyState:\"live\"}},sender:{track:null},stopped:false}]"
-PASS checkAddTransceiverWithTrack
-PASS checkAddTransceiverWithAddTrack
-PASS checkAddTransceiverWithDirection
-PASS checkAddTransceiverWithStream
-FAIL checkAddTransceiverWithOfferToReceiveAudio assert_equals: expected "[{currentDirection:null,direction:\"recvonly\",mid:null,receiver:{track:{kind:\"audio\"}},sender:{track:null},stopped:false}]" but got "[]"
-FAIL checkAddTransceiverWithOfferToReceiveVideo assert_equals: expected "[{currentDirection:null,direction:\"recvonly\",mid:null,receiver:{track:{kind:\"video\"}},sender:{track:null},stopped:false}]" but got "[]"
-FAIL checkAddTransceiverWithOfferToReceiveBoth assert_equals: expected "[{currentDirection:null,direction:\"recvonly\",mid:null,receiver:{track:{kind:\"audio\"}},sender:{track:null},stopped:false},{currentDirection:null,direction:\"recvonly\",mid:null,receiver:{track:{kind:\"video\"}},sender:{track:null},stopped:false}]" but got "[]"
-PASS checkAddTransceiverWithSetRemoteOfferSending
-PASS checkAddTransceiverWithSetRemoteOfferNoSend
-PASS checkAddTransceiverBadKind
-PASS checkSetDirection
-PASS checkCurrentDirection
-PASS checkSendrecvWithNoSendTrack
-PASS checkSendrecvWithTracklessStream
-PASS checkAddTransceiverNoTrackDoesntPair
-PASS checkAddTransceiverWithTrackDoesntPair
-PASS checkAddTransceiverThenReplaceTrackDoesntPair
-FAIL checkAddTransceiverThenAddTrackPairs assert_equals: expected "[{sender:{track:{}}}]" but got "[{sender:{track:{}}},{}]"
-PASS checkAddTrackPairs
-PASS checkReplaceTrackNullDoesntPreventPairing
-PASS checkRemoveAndReadd
-PASS checkAddTrackExistingTransceiverThenRemove
-FAIL checkRemoveTrackNegotiation promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType."
-FAIL checkMute assert_equals: expected "[{receiver:{track:{kind:\"audio\",muted:true}}},{receiver:{track:{kind:\"video\",muted:true}}}]" but got "[{receiver:{track:{kind:\"audio\",muted:false}}},{receiver:{track:{kind:\"video\",muted:false}}}]"
-FAIL checkOnAddStream assert_equals: Should have 1 addstream event expected 1 but got 0
-FAIL checkStop promise_test: Unhandled rejection with value: object "TypeError: stoppedTransceiver.stop is not a function"
-FAIL checkStopAfterCreateOffer promise_test: Unhandled rejection with value: object "TypeError: pc1.getTransceivers(...)[0].stop is not a function"
-FAIL checkStopAfterSetLocalOffer promise_test: Unhandled rejection with value: object "TypeError: pc1.getTransceivers(...)[0].stop is not a function"
-FAIL checkStopAfterSetRemoteOffer promise_test: Unhandled rejection with value: object "TypeError: pc2.getTransceivers(...)[0].stop is not a function"
-FAIL checkStopAfterCreateAnswer promise_test: Unhandled rejection with value: object "TypeError: pc2.getTransceivers(...)[0].stop is not a function"
-FAIL checkStopAfterSetLocalAnswer promise_test: Unhandled rejection with value: object "TypeError: pc2.getTransceivers(...)[0].stop is not a function"
-FAIL checkStopAfterClose assert_equals: Stopping a transceiver on a closed PC should throw. throws InvalidStateError expected "InvalidStateError" but got "TypeError"
-FAIL checkLocalRollback promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType."
-FAIL checkRemoteRollback promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType."
-FAIL checkMsectionReuse promise_test: Unhandled rejection with value: object "TypeError: pc2.getTransceivers(...)[0].stop is not a function"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 5b6244d8..3638301a 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -1085,33 +1085,34 @@
   // http://crbug.com/657237 for more details.
   if (!selection.IsCaret())
     return false;
-  const VisiblePosition& position = selection.VisibleStart();
+  const Position position = selection.Start();
   static const EWordSide kWordSideList[2] = {kNextWordIfOnBoundary,
                                              kPreviousWordIfOnBoundary};
   for (EWordSide word_side : kWordSideList) {
-    // TODO(yoichio): We should have Position version of |start/endOfWord|
-    // for avoiding unnecessary canonicalization.
-    VisiblePosition start = StartOfWord(position, word_side);
-    VisiblePosition end = EndOfWord(position, word_side);
+    Position start = StartOfWordPosition(position, word_side);
+    Position end = EndOfWordPosition(position, word_side);
 
     // TODO(editing-dev): |StartOfWord()| and |EndOfWord()| should not make null
     // for non-null parameter.
     // See http://crbug.com/872443
-    if (start.DeepEquivalent().IsNull() || end.DeepEquivalent().IsNull())
+    if (start.IsNull() || end.IsNull())
       continue;
 
-    String text =
-        PlainText(EphemeralRange(start.DeepEquivalent(), end.DeepEquivalent()));
+    if (start > end) {
+      // Since word boundaries are computed on flat tree, they can be reversed
+      // when mapped back to DOM.
+      std::swap(start, end);
+    }
+
+    String text = PlainText(EphemeralRange(start, end));
     if (!text.IsEmpty() && !IsSeparator(text.CharacterStartingAt(0))) {
-      SetSelection(SelectionInDOMTree::Builder()
-                       .Collapse(start.ToPositionWithAffinity())
-                       .Extend(end.DeepEquivalent())
-                       .Build(),
-                   SetSelectionOptions::Builder()
-                       .SetShouldCloseTyping(true)
-                       .SetShouldClearTypingStyle(true)
-                       .SetGranularity(TextGranularity::kWord)
-                       .Build());
+      SetSelection(
+          SelectionInDOMTree::Builder().Collapse(start).Extend(end).Build(),
+          SetSelectionOptions::Builder()
+              .SetShouldCloseTyping(true)
+              .SetShouldClearTypingStyle(true)
+              .SetGranularity(TextGranularity::kWord)
+              .Build());
       return true;
     }
   }
diff --git a/third_party/blink/renderer/core/html/anchor_element_metrics.cc b/third_party/blink/renderer/core/html/anchor_element_metrics.cc
index 78f00d2..9666026 100644
--- a/third_party/blink/renderer/core/html/anchor_element_metrics.cc
+++ b/third_party/blink/renderer/core/html/anchor_element_metrics.cc
@@ -141,8 +141,7 @@
 
 // static
 base::Optional<AnchorElementMetrics> AnchorElementMetrics::Create(
-    const HTMLAnchorElement* anchor_element,
-    bool is_url_incremented_by_one) {
+    const HTMLAnchorElement* anchor_element) {
   LocalFrame* local_frame = anchor_element->GetDocument().GetFrame();
   LayoutObject* layout_object = anchor_element->GetLayoutObject();
   if (!local_frame || !layout_object)
@@ -154,11 +153,7 @@
     return base::nullopt;
 
   IntRect viewport = root_frame_view->LayoutViewport()->VisibleContentRect();
-
-  // Do not ignore anchor elements for which |is_url_incremented_by_one| is
-  // true since there is typically high click probability for such anchor
-  // elements.
-  if (viewport.Size().IsEmpty() && !is_url_incremented_by_one)
+  if (viewport.Size().IsEmpty())
     return base::nullopt;
 
   // Use the viewport size to normalize anchor element metrics.
@@ -210,7 +205,7 @@
       ratio_distance_top_to_visible_top, ratio_distance_center_to_visible_top,
       ratio_distance_root_top, ratio_distance_root_bottom, ratio_root_height,
       IsInIFrame(*anchor_element), ContainsImage(*anchor_element),
-      IsSameHost(*anchor_element), is_url_incremented_by_one);
+      IsSameHost(*anchor_element), IsUrlIncrementedByOne(*anchor_element));
 }
 
 // static
@@ -224,8 +219,7 @@
     return base::nullopt;
   }
 
-  bool is_url_incremented_by_one = IsUrlIncrementedByOne(*anchor_element);
-  auto anchor_metrics = Create(anchor_element, is_url_incremented_by_one);
+  auto anchor_metrics = Create(anchor_element);
   if (anchor_metrics.has_value()) {
     anchor_metrics.value().RecordMetricsOnClick();
 
@@ -255,22 +249,14 @@
   for (const auto& member_element : sender->GetAnchorElements()) {
     const HTMLAnchorElement& anchor_element = *member_element;
 
-    if (!anchor_element.Href().ProtocolIsInHTTPFamily())
-      continue;
-
-    bool is_url_incremented_by_one = IsUrlIncrementedByOne(anchor_element);
-
     // We ignore anchor elements that are not in the visual viewport.
-    // Do not ignore anchor elements for which |is_url_incremented_by_one| is
-    // true since there is typically high click probability for such anchor
-    // elements.
-    if (anchor_element.VisibleBoundsInVisualViewport().IsEmpty() &&
-        !is_url_incremented_by_one) {
+    if (!anchor_element.Href().ProtocolIsInHTTPFamily() ||
+        anchor_element.VisibleBoundsInVisualViewport().IsEmpty()) {
       continue;
     }
 
     base::Optional<AnchorElementMetrics> anchor_metric =
-        Create(&anchor_element, is_url_incremented_by_one);
+        Create(&anchor_element);
     if (!anchor_metric.has_value())
       continue;
 
diff --git a/third_party/blink/renderer/core/html/anchor_element_metrics.h b/third_party/blink/renderer/core/html/anchor_element_metrics.h
index 3ecf9cf..da5949eb 100644
--- a/third_party/blink/renderer/core/html/anchor_element_metrics.h
+++ b/third_party/blink/renderer/core/html/anchor_element_metrics.h
@@ -55,16 +55,8 @@
   // browser on page load.
   static const int kMaxAnchorElementMetricsSize;
 
-  // Extract features of the anchor element. |is_url_incremented_by_one| is
-  // true if |anchor_element| links to a URL which is one number different
-  // from the document's URL. Based on the value of the anchor element URL,
-  // and document URL, |is_url_incremented_by_one| may be true or false.
-  // Examples for different values of |is_url_incremented_by_one|:
-  // example.com/page9/cat5, example.com/page10/cat5 => true
-  // example.com/page9/cat5, example.com/page10/cat10 => false
-  static base::Optional<AnchorElementMetrics> Create(
-      const HTMLAnchorElement* anchor_element,
-      bool is_url_incremented_by_one);
+  // Extract features of the anchor element.
+  static base::Optional<AnchorElementMetrics> Create(const HTMLAnchorElement*);
 
   // Returns the mojom struct used to send metrics to the browser process.
   mojom::blink::AnchorElementMetricsPtr CreateMetricsPtr() const;
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
index 9ffbb971..ff6bf15 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc
@@ -31,7 +31,6 @@
 #include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
 #include "third_party/blink/renderer/core/workers/worker_animation_frame_provider.h"
 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
-#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
@@ -48,16 +47,14 @@
   // For wide gamut color spaces, user must explicitly request half float
   // storage. Otherwise, we fall back to sRGB in uint8. Invalid requests fall
   // back to sRGB in uint8 too.
-  if (SharedGpuContext::IsGpuCompositingEnabled()) {
-    if (creation_attributes_.pixel_format == kF16CanvasPixelFormatName) {
-      color_params_.SetCanvasPixelFormat(kF16CanvasPixelFormat);
-      if (creation_attributes_.color_space == kLinearRGBCanvasColorSpaceName)
-        color_params_.SetCanvasColorSpace(kLinearRGBCanvasColorSpace);
-      if (creation_attributes_.color_space == kRec2020CanvasColorSpaceName)
-        color_params_.SetCanvasColorSpace(kRec2020CanvasColorSpace);
-      else if (creation_attributes_.color_space == kP3CanvasColorSpaceName)
-        color_params_.SetCanvasColorSpace(kP3CanvasColorSpace);
-    }
+  if (creation_attributes_.pixel_format == kF16CanvasPixelFormatName) {
+    color_params_.SetCanvasPixelFormat(kF16CanvasPixelFormat);
+    if (creation_attributes_.color_space == kLinearRGBCanvasColorSpaceName)
+      color_params_.SetCanvasColorSpace(kLinearRGBCanvasColorSpace);
+    if (creation_attributes_.color_space == kRec2020CanvasColorSpaceName)
+      color_params_.SetCanvasColorSpace(kRec2020CanvasColorSpace);
+    else if (creation_attributes_.color_space == kP3CanvasColorSpaceName)
+      color_params_.SetCanvasColorSpace(kP3CanvasColorSpace);
   }
 
   if (!creation_attributes_.alpha)
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
index 1a2ab68..6624165f 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -1211,8 +1211,9 @@
 }
 
 void HTMLDocumentParser::ScanAndPreload(HTMLPreloadScanner* scanner) {
-  PreloadRequestStream requests =
-      scanner->Scan(GetDocument()->ValidBaseElementURL(), nullptr);
+  bool seen_csp_meta_tag = false;
+  PreloadRequestStream requests = scanner->Scan(
+      GetDocument()->ValidBaseElementURL(), nullptr, seen_csp_meta_tag);
   preloader_->TakeAndPreload(requests);
 }
 
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
index 9f3fb193..787fdda 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
@@ -949,7 +949,8 @@
 
 PreloadRequestStream HTMLPreloadScanner::Scan(
     const KURL& starting_base_element_url,
-    ViewportDescriptionWrapper* viewport) {
+    ViewportDescriptionWrapper* viewport,
+    bool& has_csp_meta_tag) {
   // HTMLTokenizer::updateStateFor only works on the main thread.
   DCHECK(IsMainThread());
 
@@ -967,13 +968,14 @@
     if (token_.GetType() == HTMLToken::kStartTag)
       tokenizer_->UpdateStateFor(
           AttemptStaticStringCreation(token_.GetName(), kLikely8Bit));
-    bool is_csp_meta_tag = false;
-    scanner_.Scan(token_, source_, requests, viewport, &is_csp_meta_tag);
+    bool seen_csp_meta_tag = false;
+    scanner_.Scan(token_, source_, requests, viewport, &seen_csp_meta_tag);
+    has_csp_meta_tag |= seen_csp_meta_tag;
     token_.Clear();
     // Don't preload anything if a CSP meta tag is found. We should rarely find
     // them here because the HTMLPreloadScanner is only used for the synchronous
     // parsing path.
-    if (is_csp_meta_tag) {
+    if (seen_csp_meta_tag) {
       // Reset the tokenizer, to avoid re-scanning tokens that we are about to
       // start parsing.
       source_.Clear();
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
index 4905f0c..56bbeab 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
@@ -189,7 +189,8 @@
 
   void AppendToEnd(const SegmentedString&);
   PreloadRequestStream Scan(const KURL& document_base_element_url,
-                            ViewportDescriptionWrapper*);
+                            ViewportDescriptionWrapper*,
+                            bool& has_csp_meta_tag);
 
  private:
   HTMLPreloadScanner(const HTMLParserOptions&,
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner_fuzzer.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner_fuzzer.cc
index 0e934a41..4d20f391 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner_fuzzer.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner_fuzzer.cc
@@ -75,7 +75,9 @@
   CString bytes = fuzzed_data.ConsumeRemainingBytes();
   String decoded_bytes = decoder.Decode(bytes.data(), bytes.length());
   scanner->AppendToEnd(decoded_bytes);
-  PreloadRequestStream requests = scanner->Scan(document_url, nullptr);
+  bool has_csp_meta_tag_unused = false;
+  PreloadRequestStream requests =
+      scanner->Scan(document_url, nullptr, has_csp_meta_tag_unused);
   preloader.TakeAndPreload(requests);
   return 0;
 }
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
index 4e189a85..87cc5c4 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
@@ -61,6 +61,12 @@
   network::mojom::FetchCredentialsMode credentials_mode;
 };
 
+struct CSPTestCase {
+  const char* base_url;
+  const char* input_html;
+  bool should_see_csp_tag;
+};
+
 struct NonceTestCase {
   const char* base_url;
   const char* input_html;
@@ -253,7 +259,8 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
 
     preloader.PreloadRequestVerification(
@@ -265,7 +272,8 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
     preloader.PreconnectRequestVerification(test_case.preconnected_host,
                                             test_case.cross_origin);
@@ -275,7 +283,8 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
 
     if (test_case.expected_referrer) {
@@ -294,20 +303,29 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
-
     preloader.CORSRequestVerification(&GetDocument(), test_case.request_mode,
                                       test_case.credentials_mode);
   }
 
+  void Test(CSPTestCase test_case) {
+    HTMLMockHTMLResourcePreloader preloader;
+    KURL base_url(test_case.base_url);
+    seen_csp_meta_tag_ = false;
+    scanner_->AppendToEnd(String(test_case.input_html));
+    scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
+    EXPECT_EQ(test_case.should_see_csp_tag, seen_csp_meta_tag_);
+  }
+
   void Test(NonceTestCase test_case) {
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
-
     preloader.NonceRequestVerification(test_case.nonce);
   }
 
@@ -315,7 +333,8 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
 
     preloader.ContextVerification(test_case.is_image_set);
@@ -326,7 +345,8 @@
     HTMLMockHTMLResourcePreloader preloader;
     KURL base_url("http://example.test/");
     scanner_->AppendToEnd(String(test_case.input_html));
-    PreloadRequestStream requests = scanner_->Scan(base_url, nullptr);
+    PreloadRequestStream requests =
+        scanner_->Scan(base_url, nullptr, seen_csp_meta_tag_);
     preloader.TakeAndPreload(requests);
 
     preloader.CheckNumberOfIntegrityConstraints(
@@ -335,6 +355,7 @@
 
  private:
   std::unique_ptr<HTMLPreloadScanner> scanner_;
+  bool seen_csp_meta_tag_ = false;
 };
 
 TEST_F(HTMLPreloadScannerTest, testImages) {
@@ -889,6 +910,22 @@
   }
 }
 
+TEST_F(HTMLPreloadScannerTest, testCSP) {
+  CSPTestCase test_cases[] = {
+      {"http://example.test",
+       "<meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+       "https:\">",
+       true},
+      {"http://example.test",
+       "<meta name=\"viewport\" content=\"width=device-width\">", false},
+      {"http://example.test", "<img src=\"example.gif\">", false}};
+
+  for (const auto& test_case : test_cases) {
+    SCOPED_TRACE(test_case.input_html);
+    Test(test_case);
+  }
+}
+
 TEST_F(HTMLPreloadScannerTest, testNonce) {
   NonceTestCase test_cases[] = {
       {"http://example.test", "<script src='/script'></script>", ""},
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.h b/third_party/blink/renderer/core/layout/layout_block_flow.h
index 2a7cbc5..9a000f4c 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.h
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.h
@@ -821,6 +821,9 @@
 
   int GetLayoutPassCountForTesting();
 
+  // This is public only for use by LayoutNG, so that NGBlockNode can call it.
+  void IncrementLayoutPassCount();
+
  protected:
   LayoutUnit MaxPositiveMarginBefore() const {
     return rare_data_
@@ -1054,7 +1057,6 @@
   // Positions new floats and also adjust all floats encountered on the line if
   // any of them have to move to the next page/column.
   void PositionDialog();
-  void IncrementLayoutPassCount();
 
   // END METHODS DEFINED IN LayoutBlockFlowLine
 };
diff --git a/third_party/blink/renderer/core/layout/layout_grid.cc b/third_party/blink/renderer/core/layout/layout_grid.cc
index e8882aa..2df38ce 100644
--- a/third_party/blink/renderer/core/layout/layout_grid.cc
+++ b/third_party/blink/renderer/core/layout/layout_grid.cc
@@ -1403,8 +1403,18 @@
       direction == kForColumns ? offset_between_columns_ : offset_between_rows_;
   auto& positions = is_row_axis ? column_positions_ : row_positions_;
   positions.resize(number_of_lines);
+
   auto border_and_padding =
       is_row_axis ? BorderAndPaddingLogicalLeft() : BorderAndPaddingBefore();
+  if (is_row_axis) {
+    if (StyleRef().IsHorizontalWritingMode() &&
+        !StyleRef().IsLeftToRightDirection())
+      border_and_padding += ScrollbarLogicalWidth();
+  } else {
+    if (StyleRef().GetWritingMode() == WritingMode::kVerticalRl)
+      border_and_padding += ScrollbarLogicalHeight();
+  }
+
   positions[0] = border_and_padding + offset.position_offset;
   if (number_of_lines > 1) {
     // If we have collapsed tracks we just ignore gaps here and add them later
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 caa674e..bc840f270 100644
--- a/third_party/blink/renderer/core/layout/layout_object_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_test.cc
@@ -893,13 +893,13 @@
   Document* iframe_doc = frame_owner_element->contentDocument();
   Element* target = iframe_doc->getElementById("target");
   HitTestResult result = target->GetLayoutObject()->HitTestForOcclusion();
-  EXPECT_TRUE(result.InnerNode() == target);
+  EXPECT_EQ(result.InnerNode(), target);
 
   Element* occluder = GetDocument().getElementById("occluder");
   occluder->SetInlineStyleProperty(CSSPropertyMarginTop, "-150px");
   GetDocument().View()->UpdateAllLifecyclePhases();
   result = target->GetLayoutObject()->HitTestForOcclusion();
-  EXPECT_TRUE(result.InnerNode() == occluder);
+  EXPECT_EQ(result.InnerNode(), occluder);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 4cd42c0..246e300 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -181,6 +181,9 @@
 
   LayoutBlockFlow* block_flow =
       box_->IsLayoutNGMixin() ? ToLayoutBlockFlow(box_) : nullptr;
+  if (RuntimeEnabledFeatures::TrackLayoutPassesPerBlockEnabled() && block_flow)
+    block_flow->IncrementLayoutPassCount();
+
   NGLayoutInputNode first_child = FirstChild();
   scoped_refptr<NGLayoutResult> layout_result;
   if (block_flow) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 60d94a5..a5f9add 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -2660,10 +2660,7 @@
 void AXLayoutObject::HandleAutofillStateChanged(bool is_available) {
   if (is_autofill_available_ != is_available) {
     is_autofill_available_ = is_available;
-    // Reusing the value change event in order to invalidate, even though the
-    // value did not necessarily change.
-    // TODO(dmazzoni) change to using a MarkDirty() API.
-    AXObjectCache().PostNotification(this, ax::mojom::Event::kValueChanged);
+    AXObjectCache().MarkAXObjectDirty(this, false);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index b40fd27e..f43a06c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -693,11 +693,7 @@
     ChildrenChanged(element->parentNode());
   } else {
     // Refresh the focusable state on the exposed object.
-    // Reusing the value change event in order to invalidate, even though the
-    // value did not necessarily change.
-    // TODO(accessibility) find out why using MarkAXObjectDirty(obj, false) does
-    // not cause a state change event to be emitted.
-    PostNotification(obj, ax::mojom::Event::kValueChanged);
+    MarkAXObjectDirty(obj, false);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node.cc b/third_party/blink/renderer/modules/webaudio/audio_node.cc
index 9fa74340..bc76efec 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_node.cc
@@ -450,10 +450,9 @@
     // the outputs so that the tail for the node can be output.
     // Otherwise, we can disable the outputs right away.
     if (RequiresTailProcessing()) {
-      if (Context()->ContextState() !=
-          BaseAudioContext::AudioContextState::kClosed) {
-        Context()->GetDeferredTaskHandler().AddTailProcessingHandler(this);
-      }
+      auto& deferred_task_handler = Context()->GetDeferredTaskHandler();
+      if (deferred_task_handler.AcceptsTailProcessing())
+        deferred_task_handler.AddTailProcessingHandler(this);
     } else {
       DisableOutputs();
     }
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
index f97328b..eb0c9d7 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
@@ -612,6 +612,9 @@
                              WrapPersistent(this)));
   }
 
+  if (new_state == kClosed)
+    GetDeferredTaskHandler().StopAcceptingTailProcessing();
+
   // Notify context that state changed
   if (GetExecutionContext()) {
     GetExecutionContext()
diff --git a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.cc b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.cc
index 0e27a15..5174ef59 100644
--- a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.cc
@@ -167,6 +167,7 @@
 
 void DeferredTaskHandler::AddTailProcessingHandler(
     scoped_refptr<AudioHandler> handler) {
+  DCHECK(accepts_tail_processing_);
   AssertGraphOwner();
 
   if (!tail_processing_handlers_.Contains(handler)) {
diff --git a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
index 4282936..0bc00da3 100644
--- a/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/deferred_task_handler.h
@@ -109,6 +109,9 @@
   void RequestToDeleteHandlersOnMainThread();
   void ClearHandlersToBeDeleted();
 
+  bool AcceptsTailProcessing() const { return accepts_tail_processing_; }
+  void StopAcceptingTailProcessing() { accepts_tail_processing_ = false; }
+
   // If |node| requires tail processing, add it to the list of tail
   // nodes so the tail is processed.
   void AddTailProcessingHandler(scoped_refptr<AudioHandler>);
@@ -228,11 +231,16 @@
 
   // Nodes that are processing its tail.
   Vector<scoped_refptr<AudioHandler>> tail_processing_handlers_;
+
   // Tail processing nodes that are now finished and want the output to be
   // disabled.  This is updated in the audio thread (with the graph lock).  The
   // main thread will disable the outputs.
   Vector<scoped_refptr<AudioHandler>> finished_tail_processing_handlers_;
 
+  // Once the associated context closes, new tail processing handlers are not
+  // accepted.
+  bool accepts_tail_processing_ = true;
+
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   // Graph locking.
diff --git a/third_party/minizip/README.chromium b/third_party/minizip/README.chromium
index e6602fe..85a56c2 100644
--- a/third_party/minizip/README.chromium
+++ b/third_party/minizip/README.chromium
@@ -1,8 +1,8 @@
 Name: nmoinvaz/minizip
 Short name: minizip
 URL: https://github.com/nmoinvaz/minizip
-Version: 2.7.4
-Revision: c47090678d687742eddc60d07c5430279af416d8
+Version: 0
+Revision: 4d4c9db5b019e71b4a40fb41ab21fb47de12ae69
 Security critical: yes
 License: Custom license
 License File: src/LICENSE
diff --git a/third_party/zlib/arm_features.c b/third_party/zlib/arm_features.c
index 60acbac..a91030d 100644
--- a/third_party/zlib/arm_features.c
+++ b/third_party/zlib/arm_features.c
@@ -7,6 +7,11 @@
 #include "arm_features.h"
 
 #include "zutil.h"
+
+int ZLIB_INTERNAL arm_cpu_enable_crc32 = 0;
+int ZLIB_INTERNAL arm_cpu_enable_pmull = 0;
+
+#if !defined(_MSC_VER)
 #include <pthread.h>
 #include <stdint.h>
 
@@ -19,9 +24,6 @@
 #error ### No ARM CPU features detection in your platform/OS
 #endif
 
-int ZLIB_INTERNAL arm_cpu_enable_crc32 = 0;
-int ZLIB_INTERNAL arm_cpu_enable_pmull = 0;
-
 static pthread_once_t cpu_check_inited_once = PTHREAD_ONCE_INIT;
 
 static void init_arm_features(void)
@@ -58,3 +60,31 @@
 {
     pthread_once(&cpu_check_inited_once, init_arm_features);
 }
+#else
+#include <windows.h>
+
+static BOOL CALLBACK _arm_check_features(PINIT_ONCE once,
+                                         PVOID param,
+                                         PVOID *context);
+static INIT_ONCE cpu_check_inited_once = INIT_ONCE_STATIC_INIT;
+
+
+void ZLIB_INTERNAL arm_check_features(void)
+{
+    InitOnceExecuteOnce(&cpu_check_inited_once, _arm_check_features,
+                        NULL, NULL);
+}
+
+static BOOL CALLBACK _arm_check_features(PINIT_ONCE once,
+                                         PVOID param,
+                                         PVOID *context)
+{
+    if (IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE))
+        arm_cpu_enable_crc32 = 1;
+
+    if (IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE))
+        arm_cpu_enable_pmull = 1;
+
+    return TRUE;
+}
+#endif /* _MSC_VER */
diff --git a/tools/binary_size/libsupersize/archive.py b/tools/binary_size/libsupersize/archive.py
index 0b879f4..95a440b372 100644
--- a/tools/binary_size/libsupersize/archive.py
+++ b/tools/binary_size/libsupersize/archive.py
@@ -601,7 +601,7 @@
       SRC_ROOT.
     cache: Dict of OWNERS paths. Used instead of filesystem if paths are present
       in the dict.
-    knobs: Instance of SectionSizeKnobs. Tunable knobs and options.
+    knobs: Instance of SectionSizeKnobs with tunable knobs and options.
 
   Returns:
     COMPONENT belonging to |start_path|, or empty string if not found.
@@ -736,7 +736,7 @@
     apk_path: Path to the .apk file to measure.
     tool_prefix: Prefix for c++filt & nm.
     output_directory: Build output directory.
-    linker_name: 'gold', 'lld_v#' (# is a number), 'lld-lto_v#', or None.
+    linker_name: A coded linker name (see linker_map_parser.py).
 
   Returns:
     None if |elf_path| is not supplied. Otherwise returns dict mapping string
@@ -1194,13 +1194,20 @@
 
   Args:
     map_path: Path to the linker .map(.gz) file to parse.
-    elf_path: Path to the corresponding unstripped ELF file. Used to find symbol
-        aliases and inlined functions. Can be None.
     tool_prefix: Prefix for c++filt & nm (required).
     output_directory: Build output directory. If None, source_paths and symbol
         alias information will not be recorded.
+    elf_path: Path to the corresponding unstripped ELF file. Used to find symbol
+        aliases and inlined functions. Can be None.
+    apk_path: Path to the .apk file to measure.
     track_string_literals: Whether to break down "** merge string" sections into
         smaller symbols (requires output_directory).
+    metadata: Metadata dict from CreateMetadata().
+    apk_so_path: Path to an .so file within an APK file.
+    pak_files: List of paths to .pak files.
+    pak_info_file: Path to a .pak.info file.
+    linker_name: A coded linker name (see linker_map_parser.py).
+    knobs: Instance of SectionSizeKnobs with tunable knobs and options.
 
   Returns:
     A tuple of (section_sizes, raw_symbols).
diff --git a/tools/binary_size/libsupersize/linker_map_parser.py b/tools/binary_size/libsupersize/linker_map_parser.py
index 7111e5c..9ba20f0 100755
--- a/tools/binary_size/libsupersize/linker_map_parser.py
+++ b/tools/binary_size/libsupersize/linker_map_parser.py
@@ -3,7 +3,20 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Parser for linker map files."""
+"""Parser for linker map files.
+
+The format of a linker map file depends on the linker that generates it. This
+file uses "coded linker name" to identify formats and variants:
+
+  'gold': The gold linker (usage is being deprecated by Chrome).
+  'lld_v0': LLD linker (no LTO), old format.
+  'lld-lto_v0': LLD linker with ThinLTO, old format.
+  'lld_v1': LLD linker (no LTO), new format.
+  'lld-lto_v1': LLD linker with ThinLTO, new format.
+
+The |linker_name| parameter in various functions must take one of the above
+coded linker name values.
+"""
 
 from __future__ import print_function
 
@@ -469,7 +482,7 @@
     lines: Iterable of lines from the linker map.
 
   Returns:
-    A coded linker name: 'gold', 'lld_v#' (# is a number), or 'lld-lto_v#'.
+    A coded linker name.
 
   """
   first_line = next(lines)
diff --git a/tools/grit/grit/format/chrome_messages_json.py b/tools/grit/grit/format/chrome_messages_json.py
index 27a9da1..43853547 100644
--- a/tools/grit/grit/format/chrome_messages_json.py
+++ b/tools/grit/grit/format/chrome_messages_json.py
@@ -15,15 +15,11 @@
 
 def Format(root, lang='en', output_dir='.'):
   """Format the messages as JSON."""
-  yield '{\n'
+  yield '{'
 
   encoder = JSONEncoder(ensure_ascii=False)
-  format = ('  "%s": {\n'
-            '    "message": %s%s\n'
-            '  }')
-  placeholder_format = ('      "%i": {\n'
-                        '        "content": "$%i"\n'
-                        '      }')
+  format = '"%s":{"message":%s%s}'
+  placeholder_format = '"%i":{"content":"$%i"}'
   first = True
   for child in root.ActiveDescendants():
     if isinstance(child, message.MessageNode):
@@ -50,15 +46,15 @@
           break
         loc_message = loc_message.replace('$%d' % i, '$%d$' % i)
         if placeholders:
-          placeholders += ',\n'
+          placeholders += ','
         placeholders += placeholder_format % (i, i)
 
       if not first:
-        yield ',\n'
+        yield ','
       first = False
 
       if placeholders:
-        placeholders = ',\n    "placeholders": {\n%s\n    }' % placeholders
+        placeholders = ',"placeholders":{%s}' % placeholders
       yield format % (id, loc_message, placeholders)
 
-  yield '\n}\n'
+  yield '}'
diff --git a/tools/grit/grit/format/chrome_messages_json_unittest.py b/tools/grit/grit/format/chrome_messages_json_unittest.py
index cf6de6c7..ae9c7a6 100755
--- a/tools/grit/grit/format/chrome_messages_json_unittest.py
+++ b/tools/grit/grit/format/chrome_messages_json_unittest.py
@@ -6,6 +6,7 @@
 """Unittest for chrome_messages_json.py.
 """
 
+import json
 import os
 import sys
 if __name__ == '__main__':
@@ -20,6 +21,10 @@
 
 class ChromeMessagesJsonFormatUnittest(unittest.TestCase):
 
+  # The default unittest diff limit is too low for our unittests.
+  # Allow the framework to show the full diff output all the time.
+  maxDiff = None
+
   def testMessages(self):
     root = util.ParseGrdForUnittest(u"""
     <messages>
@@ -96,7 +101,7 @@
   }
 }
 """
-    self.assertEqual(test.strip(), output.strip())
+    self.assertEqual(json.loads(test), json.loads(output))
 
   def testTranslations(self):
     root = util.ParseGrdForUnittest("""
@@ -121,7 +126,7 @@
   }
 }
 """
-    self.assertEqual(test.strip(), output.strip())
+    self.assertEqual(json.loads(test), json.loads(output))
 
   def testSkipMissingTranslations(self):
     grd = """<?xml version="1.0" encoding="UTF-8"?>
@@ -141,12 +146,25 @@
     build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'fr'),
                                 buf)
     output = buf.getvalue()
-    test = u"""
-{
+    test = u'{}'
+    self.assertEqual(test, output)
 
-}
-"""
-    self.assertEqual(test.strip(), output.strip())
+  def testVerifyMinification(self):
+    root = util.ParseGrdForUnittest(u"""
+    <messages>
+      <message name="IDS">
+        <ph name="BEGIN">$1<ex>a</ex></ph>test<ph name="END">$2<ex>b</ex></ph>
+      </message>
+    </messages>
+    """)
+
+    buf = StringIO.StringIO()
+    build.RcBuilder.ProcessNode(root, DummyOutput('chrome_messages_json', 'en'),
+                                buf)
+    output = buf.getvalue()
+    test = (u'{"IDS":{"message":"$1$test$2$","placeholders":'
+            u'{"1":{"content":"$1"},"2":{"content":"$2"}}}}')
+    self.assertEqual(test, output)
 
 
 class DummyOutput(object):
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 74da157..c852ada 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -17216,6 +17216,7 @@
 </action>
 
 <action name="Signin_Signin_FromForceSigninWarning">
+  <obsolete>Deprecated as the warning dialog is no longer used.</obsolete>
   <owner>zmin@chromium.org</owner>
   <description>
     Record when a user signs in again from the force sign in auth error warning
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0c48007..aa6152b79 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17263,6 +17263,7 @@
   <int value="1290" label="TABS_GOBACK"/>
   <int value="1291"
       label="BRAILLEDISPLAYPRIVATE_UPDATEBLUETOOTHBRAILLEDISPLAYADDRESS"/>
+  <int value="1292" label="AUTOTESTPRIVATE_SETASSISTANTENABLED"/>
 </enum>
 
 <enum name="ExtensionIconState">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 495a3571..796e8d8d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -24922,6 +24922,9 @@
 </histogram>
 
 <histogram name="EasyUnlock.AuthProximity.TimeSinceLastZeroRssi" units="ms">
+  <obsolete>
+    Deprecated as of 11/2018.
+  </obsolete>
   <owner>tengs@chromium.org</owner>
   <owner>xiaowenx@chromium.org</owner>
   <summary>
@@ -24939,6 +24942,9 @@
 </histogram>
 
 <histogram name="EasyUnlock.AuthProximity.TransmitPowerDelta" units="dBm">
+  <obsolete>
+    Deprecated as of 11/2018.
+  </obsolete>
   <owner>tengs@chromium.org</owner>
   <owner>xiaowenx@chromium.org</owner>
   <summary>
@@ -32105,6 +32111,17 @@
   </summary>
 </histogram>
 
+<histogram name="Extensions.IsRenderedIconSufficientlyVisibleTime"
+    units="microseconds" expires_after="2019-12-01">
+  <owner>dbertoni@chromium.org</owner>
+  <owner>extensions-core@chromium.org</owner>
+  <summary>
+    The amount of elapsed time taken to render an icon against a specified
+    background color and determine whether it would be visible to the user.
+    Recorded every time the analysis code is called.
+  </summary>
+</histogram>
+
 <histogram name="Extensions.LoadAll">
   <owner>rdevlin.cronin@chromium.org</owner>
   <summary>The number of extensions and themes loaded at profile open.</summary>
@@ -51139,7 +51156,7 @@
 </histogram>
 
 <histogram name="MobileStartup.LoadedHomepageOnColdStart" enum="BooleanIsNtp"
-    expires_after="M72">
+    expires_after="M75">
   <owner>tedchoc@chromium.org</owner>
   <owner>danielpark@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
diff --git a/tools/perf/benchmarks/system_health_smoke_test.py b/tools/perf/benchmarks/system_health_smoke_test.py
index 73afc74..0fe5aa7 100644
--- a/tools/perf/benchmarks/system_health_smoke_test.py
+++ b/tools/perf/benchmarks/system_health_smoke_test.py
@@ -91,6 +91,7 @@
   'system_health.memory_desktop/load:news:cnn',
   'system_health.memory_desktop/load:news:wikipedia',
   'system_health.memory_desktop/load:search:baidu',
+  'system_health.memory_desktop/load:search:google',
   'system_health.memory_desktop/load:search:yahoo',
   'system_health.memory_desktop/load:search:yandex',
   'system_health.memory_desktop/load:tools:stackoverflow',
@@ -99,6 +100,7 @@
   'system_health.memory_mobile/load:media:google_images',
   'system_health.memory_mobile/load:news:wikipedia',
   'system_health.memory_mobile/load:search:baidu',
+  'system_health.memory_mobile/load:search:google',
   'system_health.memory_mobile/load:search:yahoo',
   'system_health.memory_mobile/load:search:yandex',
 
diff --git a/tools/perf/page_sets/data/system_health_desktop.json b/tools/perf/page_sets/data/system_health_desktop.json
index ccf2069..c8b43a7 100644
--- a/tools/perf/page_sets/data/system_health_desktop.json
+++ b/tools/perf/page_sets/data/system_health_desktop.json
@@ -234,6 +234,9 @@
         "load:search:google": {
             "DEFAULT": "system_health_desktop_022.wprgo"
         },
+        "load:search:google:2018": {
+            "DEFAULT": "system_health_desktop_b11350f30f.wprgo"
+        },
         "load:search:taobao": {
             "DEFAULT": "system_health_desktop_000.wprgo"
         },
diff --git a/tools/perf/page_sets/data/system_health_desktop_b11350f30f.wprgo.sha1 b/tools/perf/page_sets/data/system_health_desktop_b11350f30f.wprgo.sha1
new file mode 100644
index 0000000..397240a
--- /dev/null
+++ b/tools/perf/page_sets/data/system_health_desktop_b11350f30f.wprgo.sha1
@@ -0,0 +1 @@
+b11350f30fff944eff752f84e498b364be782643
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/system_health_mobile.json b/tools/perf/page_sets/data/system_health_mobile.json
index 3f83fe99..29b1b99 100644
--- a/tools/perf/page_sets/data/system_health_mobile.json
+++ b/tools/perf/page_sets/data/system_health_mobile.json
@@ -201,6 +201,9 @@
         "load:search:google": {
             "DEFAULT": "system_health_mobile_029.wprgo"
         },
+        "load:search:google:2018": {
+            "DEFAULT": "system_health_mobile_09493f5937.wprgo"
+        },
         "load:search:taobao": {
             "DEFAULT": "system_health_mobile_000.wprgo"
         },
diff --git a/tools/perf/page_sets/data/system_health_mobile_09493f5937.wprgo.sha1 b/tools/perf/page_sets/data/system_health_mobile_09493f5937.wprgo.sha1
new file mode 100644
index 0000000..1ac13cac
--- /dev/null
+++ b/tools/perf/page_sets/data/system_health_mobile_09493f5937.wprgo.sha1
@@ -0,0 +1 @@
+09493f5937a66e59fc9bd561e160293b17ddec52
\ No newline at end of file
diff --git a/tools/perf/page_sets/system_health/loading_stories.py b/tools/perf/page_sets/system_health/loading_stories.py
index c865e94..68bac45 100644
--- a/tools/perf/page_sets/system_health/loading_stories.py
+++ b/tools/perf/page_sets/system_health/loading_stories.py
@@ -31,6 +31,12 @@
   TAGS = [story_tags.YEAR_2016]
 
 
+class LoadGoogleStory2018(_LoadingStory):
+  NAME = 'load:search:google:2018'
+  URL = 'https://www.google.co.uk/search?q=pepper'
+  TAGS = [story_tags.YEAR_2018]
+
+
 class LoadBaiduStory(_LoadingStory):
   NAME = 'load:search:baidu'
   URL = 'https://www.baidu.com/s?word=google'
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 3d33dd9..4dc34fd 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -147,16 +147,6 @@
     if (use_glib) {
       configs += [ "//build/config/linux:glib" ]
     }
-
-    if (use_x11) {
-      sources += [ "platform/atk_util_auralinux_x11.cc" ]
-
-      configs += [ "//build/config/linux:x11" ]
-      public_deps += [
-        "//ui/events/x",
-        "//ui/gfx/x",
-      ]
-    }
   }
 
   if (use_aura) {
@@ -252,10 +242,7 @@
     libs = [ "oleacc.lib" ]
   }
   if (use_atk) {
-    sources += [
-      "platform/atk_util_auralinux_unittest.cc",
-      "platform/ax_platform_node_auralinux_unittest.cc",
-    ]
+    sources += [ "platform/ax_platform_node_auralinux_unittest.cc" ]
     configs += [ "//build/config/linux/atk" ]
   }
 }
diff --git a/ui/accessibility/platform/DEPS b/ui/accessibility/platform/DEPS
deleted file mode 100644
index d457dd7d..0000000
--- a/ui/accessibility/platform/DEPS
+++ /dev/null
@@ -1,5 +0,0 @@
-specific_include_rules = {
-  "atk_util_auralinux_x11.cc": [
-    "+ui/events/x",
-  ]
-}
diff --git a/ui/accessibility/platform/atk_util_auralinux.cc b/ui/accessibility/platform/atk_util_auralinux.cc
index 85895d8..eab53e1 100644
--- a/ui/accessibility/platform/atk_util_auralinux.cc
+++ b/ui/accessibility/platform/atk_util_auralinux.cc
@@ -3,8 +3,6 @@
 // found in the LICENSE file.
 
 #include <atk/atk.h>
-#include <map>
-#include <utility>
 
 #include "base/environment.h"
 #include "base/memory/singleton.h"
@@ -77,25 +75,6 @@
   return "1.0";
 }
 
-static std::map<guint, std::pair<AtkKeySnoopFunc, gpointer>>
-    g_key_snoop_function_map;
-
-static guint atk_util_add_key_event_listener(AtkKeySnoopFunc key_snoop_function,
-                                             gpointer data) {
-  static guint current_key_event_listener_id = 0;
-
-  current_key_event_listener_id++;
-  g_key_snoop_function_map[current_key_event_listener_id] =
-      std::make_pair(key_snoop_function, data);
-  return current_key_event_listener_id;
-}
-
-static void atk_util_remove_key_event_listener(guint listener_id) {
-  auto it = g_key_snoop_function_map.find(listener_id);
-  if (it != g_key_snoop_function_map.end())
-    g_key_snoop_function_map.erase(it);
-}
-
 static void atk_util_auralinux_class_init(AtkUtilAuraLinuxClass *klass) {
   AtkUtilClass *atk_class;
   gpointer data;
@@ -106,8 +85,6 @@
   atk_class->get_root = atk_util_auralinux_get_root;
   atk_class->get_toolkit_name = atk_util_auralinux_get_toolkit_name;
   atk_class->get_toolkit_version = atk_util_auralinux_get_toolkit_version;
-  atk_class->add_key_event_listener = atk_util_add_key_event_listener;
-  atk_class->remove_key_event_listener = atk_util_remove_key_event_listener;
 }
 
 G_END_DECLS
@@ -153,32 +130,4 @@
   InitializeAsync();
 }
 
-// static
-DiscardAtkKeyEvent AtkUtilAuraLinux::HandleAtkKeyEvent(
-    AtkKeyEventStruct* key_event) {
-  DCHECK(key_event);
-
-  if (!GetInstance()->ShouldEnableAccessibility())
-    return DiscardAtkKeyEvent::Retain;
-
-  GetInstance()->InitializeAsync();
-
-  bool discard = false;
-  for (auto it = g_key_snoop_function_map.begin();
-       it != g_key_snoop_function_map.end(); it++) {
-    AtkKeySnoopFunc key_snoop_function = it->second.first;
-    gpointer data = it->second.second;
-    if (key_snoop_function(key_event, data) != 0)
-      discard = true;
-  }
-  return discard ? DiscardAtkKeyEvent::Discard : DiscardAtkKeyEvent::Retain;
-}
-
-#if !defined(USE_X11)
-DiscardAtkKeyEvent AtkUtilAuraLinux::HandleKeyEvent(
-    const ui::KeyEvent& ui_key_event) {
-  NOTREACHED();
-}
-#endif
-
 }  // namespace ui
diff --git a/ui/accessibility/platform/atk_util_auralinux.h b/ui/accessibility/platform/atk_util_auralinux.h
index b846d5f..286d49a 100644
--- a/ui/accessibility/platform/atk_util_auralinux.h
+++ b/ui/accessibility/platform/atk_util_auralinux.h
@@ -5,34 +5,12 @@
 #ifndef UI_ACCESSIBILITY_AX_UTIL_AURALINUX_H_
 #define UI_ACCESSIBILITY_AX_UTIL_AURALINUX_H_
 
-#include <atk/atk.h>
-
 #include "base/macros.h"
 #include "base/memory/singleton.h"
 #include "ui/accessibility/ax_export.h"
 
-#if defined(USE_X11)
-#include "ui/gfx/x/x11.h"
-#endif
-
 namespace ui {
 
-// These values are duplicates of the GDK values that can be found in
-// <gdk/gdktypes.h>. ATK expects the GDK values, but we don't want to depend on
-// GDK here.
-typedef enum {
-  kAtkShiftMask = 1 << 0,
-  kAtkLockMask = 1 << 1,
-  kAtkControlMask = 1 << 2,
-  kAtkMod1Mask = 1 << 3,
-  kAtkMod2Mask = 1 << 4,
-  kAtkMod3Mask = 1 << 5,
-  kAtkMod4Mask = 1 << 6,
-  KAtkMod5Mask = 1 << 7,
-} AtkKeyModifierMask;
-
-enum DiscardAtkKeyEvent { Discard, Retain };
-
 // This singleton class initializes ATK (accessibility toolkit) and
 // registers an implementation of the AtkUtil class, a global class that
 // every accessible application needs to register once.
@@ -46,12 +24,6 @@
   void InitializeAsync();
   void InitializeForTesting();
 
-  static DiscardAtkKeyEvent HandleAtkKeyEvent(AtkKeyEventStruct* key_event);
-
-#if defined(USE_X11)
-  static DiscardAtkKeyEvent HandleKeyEvent(XEvent* xevent);
-#endif
-
  private:
   friend struct base::DefaultSingletonTraits<AtkUtilAuraLinux>;
 
diff --git a/ui/accessibility/platform/atk_util_auralinux_unittest.cc b/ui/accessibility/platform/atk_util_auralinux_unittest.cc
deleted file mode 100644
index 85d88e2..0000000
--- a/ui/accessibility/platform/atk_util_auralinux_unittest.cc
+++ /dev/null
@@ -1,77 +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.
-
-// Chromium cannot upgrade to ATK 2.12 API as it still needs to run
-// valid builds for Ubuntu Trusty.
-#define ATK_DISABLE_DEPRECATION_WARNINGS
-
-#include <atk/atk.h>
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/accessibility/platform/atk_util_auralinux.h"
-#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
-#include "ui/accessibility/platform/ax_platform_node_unittest.h"
-#include "ui/accessibility/platform/test_ax_node_wrapper.h"
-
-namespace ui {
-
-class AtkUtilAuraLinuxTest : public AXPlatformNodeTest {
- public:
-  AtkUtilAuraLinuxTest() {
-    // We need to create a platform node in order to install it as the root
-    // ATK node. The ATK bridge will complain if we try to use it without a
-    // root node installed.
-    AXNodeData root;
-    root.id = 1;
-    Init(root);
-
-    TestAXNodeWrapper* wrapper =
-        TestAXNodeWrapper::GetOrCreate(tree_.get(), GetRootNode());
-    if (!wrapper)
-      NOTREACHED();
-    AXPlatformNodeAuraLinux::SetApplication(wrapper->ax_platform_node());
-
-    AtkUtilAuraLinux::GetInstance()->InitializeForTesting();
-  }
-
-  ~AtkUtilAuraLinuxTest() override {
-    TestAXNodeWrapper* wrapper =
-        TestAXNodeWrapper::GetOrCreate(tree_.get(), GetRootNode());
-    if (!wrapper)
-      NOTREACHED();
-    g_object_unref(wrapper->ax_platform_node()->GetNativeViewAccessible());
-  }
-};
-
-TEST_F(AtkUtilAuraLinuxTest, KeySnooping) {
-  AtkKeySnoopFunc key_snoop_func = reinterpret_cast<AtkKeySnoopFunc>(
-      +[](AtkKeyEventStruct* key_event, int* keyval_seen) {
-        *keyval_seen = key_event->keyval;
-      });
-
-  int keyval_seen = 0;
-  guint listener_id = atk_add_key_event_listener(key_snoop_func, &keyval_seen);
-
-  AtkKeyEventStruct atk_key_event;
-  atk_key_event.type = ATK_KEY_EVENT_PRESS;
-  atk_key_event.state = 0;
-  atk_key_event.keyval = 55;
-  atk_key_event.keycode = 10;
-  atk_key_event.timestamp = 10;
-  atk_key_event.string = nullptr;
-  atk_key_event.length = 0;
-
-  AtkUtilAuraLinux* atk_util = AtkUtilAuraLinux::GetInstance();
-  atk_util->HandleAtkKeyEvent(&atk_key_event);
-  EXPECT_EQ(keyval_seen, 55);
-
-  atk_remove_key_event_listener(listener_id);
-
-  keyval_seen = 0;
-  atk_util->HandleAtkKeyEvent(&atk_key_event);
-
-  EXPECT_EQ(keyval_seen, 0);
-}
-
-}  // namespace ui
diff --git a/ui/accessibility/platform/atk_util_auralinux_x11.cc b/ui/accessibility/platform/atk_util_auralinux_x11.cc
deleted file mode 100644
index 143d9ca..0000000
--- a/ui/accessibility/platform/atk_util_auralinux_x11.cc
+++ /dev/null
@@ -1,60 +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 <atk/atk.h>
-
-#include "ui/accessibility/platform/atk_util_auralinux.h"
-#include "ui/events/x/events_x_utils.h"
-
-namespace ui {
-
-// static
-DiscardAtkKeyEvent AtkUtilAuraLinux::HandleKeyEvent(XEvent* xevent) {
-  if (!GetInstance()->ShouldEnableAccessibility())
-    return DiscardAtkKeyEvent::Retain;
-
-  AtkKeyEventStruct atk_key_event;
-  if (xevent->type == KeyPress)
-    atk_key_event.type = ATK_KEY_EVENT_PRESS;
-  else if (xevent->type == KeyRelease)
-    atk_key_event.type = ATK_KEY_EVENT_RELEASE;
-  else
-    NOTREACHED() << xevent->type;
-
-  XKeyEvent& xkey = xevent->xkey;
-  KeySym keysym = NoSymbol;
-  XLookupString(&xkey, nullptr, 0, &keysym, nullptr);
-
-  atk_key_event.state = xkey.state;
-  atk_key_event.keyval = keysym;
-  atk_key_event.keycode = xkey.keycode;
-  atk_key_event.timestamp = xkey.time;
-
-  // This string property matches the one that was removed from GdkEventKey. In
-  // the future, ATK clients should no longer rely on it, so we set it to null.
-  atk_key_event.string = nullptr;
-  atk_key_event.length = 0;
-
-  int flags = ui::EventFlagsFromXEvent(*xevent);
-  if (flags & ui::EF_SHIFT_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkShiftMask;
-  if (flags & ui::EF_CAPS_LOCK_ON)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkLockMask;
-  if (flags & ui::EF_CONTROL_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkControlMask;
-  if (flags & ui::EF_ALT_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkMod1Mask;
-  if (flags & ui::EF_NUM_LOCK_ON)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkMod2Mask;
-  if (flags & ui::EF_MOD3_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkMod3Mask;
-  if (flags & ui::EF_COMMAND_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::kAtkMod4Mask;
-  if (flags & ui::EF_ALTGR_DOWN)
-    atk_key_event.state |= AtkKeyModifierMask::KAtkMod5Mask;
-
-  return HandleAtkKeyEvent(&atk_key_event);
-}
-
-}  // namespace ui
diff --git a/ui/aura/window_occlusion_tracker.cc b/ui/aura/window_occlusion_tracker.cc
index 810a5d3..4e6cb1b 100644
--- a/ui/aura/window_occlusion_tracker.cc
+++ b/ui/aura/window_occlusion_tracker.cc
@@ -96,6 +96,30 @@
   env_->UnpauseWindowOcclusionTracking();
 }
 
+WindowOcclusionTracker::ScopedExclude::ScopedExclude(Window* window)
+    : window_(window) {
+  window->AddObserver(this);
+  window->env()->GetWindowOcclusionTracker()->Exclude(window_);
+}
+
+WindowOcclusionTracker::ScopedExclude::~ScopedExclude() {
+  Shutdown();
+}
+
+void WindowOcclusionTracker::ScopedExclude::OnWindowDestroying(
+    aura::Window* window) {
+  DCHECK_EQ(window_, window);
+  Shutdown();
+}
+
+void WindowOcclusionTracker::ScopedExclude::Shutdown() {
+  if (window_) {
+    window_->RemoveObserver(this);
+    window_->env()->GetWindowOcclusionTracker()->Unexclude(window_);
+    window_ = nullptr;
+  }
+}
+
 void WindowOcclusionTracker::Track(Window* window) {
   DCHECK(window);
   DCHECK(window != window->GetRootWindow());
@@ -214,7 +238,7 @@
     return false;
   }
 
-  if (WindowIsAnimated(window)) {
+  if (WindowIsAnimated(window) || WindowIsExcluded(window)) {
     SetWindowAndDescendantsAreOccluded(window, false);
     return true;
   }
@@ -282,9 +306,7 @@
     if (animator->IsAnimatingOnePropertyOf(kSkipWindowWhenPropertiesAnimated))
       return false;
     animator->RemoveObserver(this);
-    auto root_window_state_it = root_windows_.find(window->GetRootWindow());
-    if (root_window_state_it != root_windows_.end())
-      MarkRootWindowAsDirty(&root_window_state_it->second);
+    MarkRootWindowAsDirty(window->GetRootWindow());
     return true;
   });
 }
@@ -345,6 +367,10 @@
   return base::ContainsKey(animated_windows_, window);
 }
 
+bool WindowOcclusionTracker::WindowIsExcluded(Window* window) const {
+  return base::ContainsKey(excluded_windows_, window);
+}
+
 template <typename Predicate>
 void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
     Window* window,
@@ -366,12 +392,12 @@
   if (root_window_state_it->second.dirty)
     return;
   if (predicate()) {
-    MarkRootWindowAsDirty(&root_window_state_it->second);
+    MarkRootWindowStateAsDirty(&root_window_state_it->second);
     MaybeComputeOcclusion();
   }
 }
 
-void WindowOcclusionTracker::MarkRootWindowAsDirty(
+void WindowOcclusionTracker::MarkRootWindowStateAsDirty(
     RootWindowState* root_window_state) {
   // If a root window is marked as dirty and occlusion states have already been
   // recomputed |kMaxRecomputeOcclusion| times, it means that they are not
@@ -382,6 +408,14 @@
   root_window_state->dirty = true;
 }
 
+bool WindowOcclusionTracker::MarkRootWindowAsDirty(aura::Window* root_window) {
+  auto root_window_state_it = root_windows_.find(root_window);
+  if (root_window_state_it == root_windows_.end())
+    return false;
+  MarkRootWindowStateAsDirty(&root_window_state_it->second);
+  return true;
+}
+
 bool WindowOcclusionTracker::WindowOrParentIsAnimated(Window* window) const {
   while (window && !WindowIsAnimated(window))
     window = window->parent();
@@ -430,13 +464,15 @@
   // Changing the opacity of a window has no effect on the occlusion state of
   // the window or its children. It can however affect the occlusion state of
   // other windows in the tree if it is visible and not animated (animated
-  // windows aren't considered in occlusion computations).
-  return window->IsVisible() && !WindowOrParentIsAnimated(window);
+  // windows aren't considered in occlusion computations), unless it is
+  // excluded.
+  return window->IsVisible() && !WindowOrParentIsAnimated(window) &&
+         !WindowIsExcluded(window);
 }
 
 bool WindowOcclusionTracker::WindowMoveMayAffectOcclusionStates(
     Window* window) const {
-  return !WindowOrParentIsAnimated(window) &&
+  return !WindowOrParentIsAnimated(window) && !WindowIsExcluded(window) &&
          (WindowOrDescendantIsOpaque(window) ||
           WindowOrDescendantIsTrackedAndVisible(window));
 }
@@ -446,7 +482,7 @@
   DCHECK(root_window);
   RootWindowState& root_window_state = root_windows_[root_window];
   ++root_window_state.num_tracked_windows;
-  MarkRootWindowAsDirty(&root_window_state);
+  MarkRootWindowStateAsDirty(&root_window_state);
 
   // It's only useful to track the host if |window| is the first tracked window
   // under |root_window|.  All windows under the same root have the same host.
@@ -510,6 +546,27 @@
   MaybeComputeOcclusion();
 }
 
+void WindowOcclusionTracker::Exclude(Window* window) {
+  // If threre is a valid use case to exclude the same window twice
+  // (e.g. independent clients may try to exclude the same window),
+  // introduce the count.
+  DCHECK(!WindowIsExcluded(window));
+  excluded_windows_.insert(window);
+  if (window->IsVisible()) {
+    if (MarkRootWindowAsDirty(window->GetRootWindow()))
+      MaybeComputeOcclusion();
+  }
+}
+
+void WindowOcclusionTracker::Unexclude(Window* window) {
+  DCHECK(WindowIsExcluded(window));
+  excluded_windows_.erase(window);
+  if (window->IsVisible()) {
+    if (MarkRootWindowAsDirty(window->GetRootWindow()))
+      MaybeComputeOcclusion();
+  }
+}
+
 void WindowOcclusionTracker::OnLayerAnimationEnded(
     ui::LayerAnimationSequence* sequence) {
   CleanupAnimatedWindows();
@@ -651,11 +708,8 @@
     return;
 
   animator->RemoveObserver(this);
-  auto root_window_state_it = root_windows_.find(window->GetRootWindow());
-  if (root_window_state_it != root_windows_.end()) {
-    MarkRootWindowAsDirty(&root_window_state_it->second);
+  if (MarkRootWindowAsDirty(window->GetRootWindow()))
     MaybeComputeOcclusion();
-  }
 }
 
 void WindowOcclusionTracker::OnOcclusionStateChanged(
diff --git a/ui/aura/window_occlusion_tracker.h b/ui/aura/window_occlusion_tracker.h
index eb73dd4f..6258792 100644
--- a/ui/aura/window_occlusion_tracker.h
+++ b/ui/aura/window_occlusion_tracker.h
@@ -65,6 +65,31 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedPause);
   };
 
+  // Exclude the window from occlusion tracking so that a window behind the
+  // given window is still considered visible. The excluded window itself and
+  // its descendant windows, if tracked, are considered visible. This is useful
+  // for a window being dragged or resized to avoid unnecessary occlusion state
+  // change triggered by these operation, because the window bounds are
+  // temporary until it is finished.
+  // Note that this is intended to be used by window manager and not by mus
+  // client process.
+  class AURA_EXPORT ScopedExclude : public aura::WindowObserver {
+   public:
+    explicit ScopedExclude(Window* window);
+    ~ScopedExclude() override;
+
+    Window* window() { return window_; }
+
+   private:
+    // aura::WindowObserver:
+    void OnWindowDestroying(aura::Window* window) override;
+
+    void Shutdown();
+
+    Window* window_;
+    DISALLOW_COPY_AND_ASSIGN(ScopedExclude);
+  };
+
   // Start tracking the occlusion state of |window|.
   void Track(Window* window);
 
@@ -154,6 +179,9 @@
   // Returns true if |window| is in |animated_windows_|.
   bool WindowIsAnimated(Window* window) const;
 
+  // Returns true if |window| is in |excluded_windows_|.
+  bool WindowIsExcluded(Window* window) const;
+
   // If the root of |window| is not dirty and |predicate| is true, marks the
   // root of |window| as dirty. Then, calls MaybeComputeOcclusion().
   // |predicate| is not evaluated if the root of |window| is already dirty when
@@ -162,8 +190,12 @@
   void MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(Window* window,
                                                        Predicate predicate);
 
-  // Marks |root_window| as dirty.
-  void MarkRootWindowAsDirty(RootWindowState* root_window_state);
+  // Marks |root_window_state| as dirty.
+  void MarkRootWindowStateAsDirty(RootWindowState* root_window_state);
+
+  // Marks |root_window| as dirty. Returns false if none of the descendent
+  // windows in |root_window| are tracked.
+  bool MarkRootWindowAsDirty(aura::Window* root_window);
 
   // Returns true if |window| or one of its parents is in |animated_windows_|.
   bool WindowOrParentIsAnimated(Window* window) const;
@@ -206,6 +238,11 @@
   void Pause();
   void Unpause();
 
+  // Exclucde/Unexclude a window from occlusion tracking. See comment on
+  // ScopedExclude.
+  void Exclude(Window* window);
+  void Unexclude(Window* window);
+
   // ui::LayerAnimationObserver:
   void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
   void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override;
@@ -247,6 +284,10 @@
   // aborted.
   base::flat_set<Window*> animated_windows_;
 
+  // Windows that are excluded from occlustion tracking. See comment on
+  // ScopedExclude.
+  base::flat_set<Window*> excluded_windows_;
+
   // Root Windows of Windows in |tracked_windows_|.
   base::flat_map<Window*, RootWindowState> root_windows_;
 
diff --git a/ui/aura/window_occlusion_tracker_unittest.cc b/ui/aura/window_occlusion_tracker_unittest.cc
index e46ddcd..2910be9 100644
--- a/ui/aura/window_occlusion_tracker_unittest.cc
+++ b/ui/aura/window_occlusion_tracker_unittest.cc
@@ -37,6 +37,8 @@
 
   void set_window(Window* window) { window_ = window; }
 
+  void SetName(const std::string& name) { window_->SetName(name); }
+
   void set_expectation(Window::OcclusionState occlusion_state,
                        const SkRegion& occluded_region) {
     expected_occlusion_state_ = occlusion_state;
@@ -49,6 +51,7 @@
 
   void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
                                 const SkRegion& occluded_region) override {
+    SCOPED_TRACE(window_->GetName());
     ASSERT_TRUE(window_);
     EXPECT_NE(occlusion_state, Window::OcclusionState::UNKNOWN);
     EXPECT_EQ(occlusion_state, expected_occlusion_state_);
@@ -2017,4 +2020,135 @@
   EXPECT_FALSE(delegate_a->is_expecting_call());
 }
 
+// Verify that the excluded window is indeed ignored by occlusion tracking.
+TEST_F(WindowOcclusionTrackerTest, ExcludeWindow) {
+  MockWindowDelegate* delegate_a = new MockWindowDelegate();
+  delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+  CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+
+  delegate_a->SetName("WindowA");
+
+  delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+  Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 100, 100), nullptr);
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+
+  MockWindowDelegate* delegate_bb = new MockWindowDelegate();
+  delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+  CreateTrackedWindow(delegate_bb, gfx::Rect(0, 0, 10, 10), window_b);
+  EXPECT_FALSE(delegate_bb->is_expecting_call());
+
+  delegate_bb->SetName("WindowBB");
+
+  delegate_bb->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+  Window* window_c = CreateUntrackedWindow(gfx::Rect(0, 0, 100, 100), nullptr);
+  EXPECT_FALSE(delegate_bb->is_expecting_call());
+
+  {
+    // |window_b| is excluded, so its child's occlusion state becomes VISIBlE.
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+    EXPECT_TRUE(delegate_bb->is_expecting_call());
+    WindowOcclusionTracker::ScopedExclude scoped(window_b);
+    EXPECT_FALSE(delegate_bb->is_expecting_call());
+
+    // Moving |window_c| out from |window_a| will make |window_a| visible
+    // because |window_b| is ignored.
+    SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
+    delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
+                                window_a_occlusion);
+    SkRegion window_bb_occlusion;
+    window_bb_occlusion.op(SkIRect::MakeXYWH(100, 100, 100, 100),
+                           SkRegion::kUnion_Op);
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
+                                 window_bb_occlusion);
+    window_c->SetBounds(gfx::Rect(100, 100, 100, 100));
+
+    // Un-excluding wil make |window_bb| OCCLUDED.
+    delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
+                                 window_bb_occlusion);
+  }
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+  EXPECT_FALSE(delegate_bb->is_expecting_call());
+
+  {
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+    SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
+    delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
+                                window_a_occlusion);
+    EXPECT_TRUE(delegate_bb->is_expecting_call());
+    WindowOcclusionTracker::ScopedExclude scoped(window_b);
+    EXPECT_FALSE(delegate_bb->is_expecting_call());
+    EXPECT_FALSE(delegate_a->is_expecting_call());
+
+    // Moving |window_b| will not affect the occlusion status.
+    window_b->SetBounds(gfx::Rect(5, 5, 100, 100));
+
+    // Un-excluding will update the occlustion status.
+    // A's occlustion status includes all windows above a.
+    window_a_occlusion.setEmpty();
+    window_a_occlusion.op(SkIRect::MakeXYWH(5, 5, 100, 100),
+                          SkRegion::kUnion_Op);
+    window_a_occlusion.op(SkIRect::MakeXYWH(100, 100, 100, 100),
+                          SkRegion::kUnion_Op);
+    delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
+                                window_a_occlusion);
+
+    SkRegion window_bb_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
+                                 window_bb_occlusion);
+  }
+
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+  EXPECT_FALSE(delegate_bb->is_expecting_call());
+
+  {
+    delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+    SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
+    delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
+                                window_a_occlusion);
+    EXPECT_TRUE(delegate_bb->is_expecting_call());
+    WindowOcclusionTracker::ScopedExclude scoped(window_b);
+    EXPECT_FALSE(delegate_bb->is_expecting_call());
+    EXPECT_FALSE(delegate_a->is_expecting_call());
+
+    // Deleting the excluded window will un-exclude itself and recomputes the
+    // occlustion state, but should not affect the state on existing windows
+    // because it's already excluded.
+    delete window_b;
+    EXPECT_FALSE(scoped.window());
+  }
+
+  MockWindowDelegate* delegate_d = new MockWindowDelegate();
+  delegate_d->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
+  delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+
+  auto* window_d = CreateTrackedWindow(delegate_d, gfx::Rect(0, 0, 10, 10));
+  window_d->SetName("WindowD");
+
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+  EXPECT_FALSE(delegate_d->is_expecting_call());
+
+  {
+    // Make sure excluding the tracked window also works.
+    SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
+    delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
+                                window_a_occlusion);
+    WindowOcclusionTracker::ScopedExclude scoped(window_d);
+    EXPECT_FALSE(delegate_a->is_expecting_call());
+
+    // Changing opacity/bounds shouldn't change the occlusion state.
+    window_d->layer()->SetOpacity(0.5f);
+    window_d->SetBounds(gfx::Rect(0, 0, 20, 20));
+
+    // A is now visible even if |window_d| is un-excluded becaues
+    // window_d is not fully opaque.
+  }
+
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+  delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
+  window_d->layer()->SetOpacity(1.f);
+  EXPECT_FALSE(delegate_a->is_expecting_call());
+}
+
 }  // namespace aura
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
index 9fd46f65..2c378f14 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -17,7 +17,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "third_party/skia/include/core/SkPath.h"
-#include "ui/accessibility/platform/atk_util_auralinux.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/cursor_client.h"
 #include "ui/aura/client/focus_client.h"
@@ -2046,11 +2045,8 @@
       break;
     }
     case KeyPress: {
-      if (ui::AtkUtilAuraLinux::HandleKeyEvent(xev) !=
-          ui::DiscardAtkKeyEvent::Discard) {
-        ui::KeyEvent keydown_event(xev);
-        DispatchKeyEvent(&keydown_event);
-      }
+      ui::KeyEvent keydown_event(xev);
+      DispatchKeyEvent(&keydown_event);
       break;
     }
     case KeyRelease: {
@@ -2059,11 +2055,8 @@
       if (!IsActive() && !HasCapture())
         break;
 
-      if (ui::AtkUtilAuraLinux::HandleKeyEvent(xev) !=
-          ui::DiscardAtkKeyEvent::Discard) {
-        ui::KeyEvent key_event(xev);
-        DispatchKeyEvent(&key_event);
-      }
+      ui::KeyEvent key_event(xev);
+      DispatchKeyEvent(&key_event);
       break;
     }
     case ButtonPress:
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index bad9e86..74b583c 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -2206,11 +2206,8 @@
 
     widget_ = CreateTopLevelPlatformWidget();
 
-    // It's not a bug if the native view has a different number of subviews,
-    // but the rest of this test assumes it. It may or may not be worth
-    // removing that assumption at some point.
-    ASSERT_EQ(0u,
-              [[widget_->GetNativeView().GetNativeNSView() subviews] count]);
+    starting_subviews_.reset(
+        [[widget_->GetNativeView().GetNativeNSView() subviews] copy]);
 
     native_host_parent_ = new View();
     widget_->GetContentsView()->AddChildView(native_host_parent_);
@@ -2224,7 +2221,9 @@
     }
     EXPECT_EQ(kNativeViewCount, native_host_parent_->child_count());
     EXPECT_NSEQ([widget_->GetNativeView().GetNativeNSView() subviews],
-                (@[ hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() ]));
+                ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                  hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view()
+                ]]));
   }
 
   void TearDown() override {
@@ -2236,9 +2235,12 @@
     return widget_->GetNativeView().GetNativeNSView();
   }
 
+  NSArray<NSView*>* GetStartingSubviews() { return starting_subviews_; }
+
   Widget* widget_ = nullptr;
   View* native_host_parent_ = nullptr;
   std::vector<std::unique_ptr<NativeHostHolder>> hosts_;
+  base::scoped_nsobject<NSArray<NSView*>> starting_subviews_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacViewsOrderTest);
@@ -2249,22 +2251,30 @@
 TEST_F(NativeWidgetMacViewsOrderTest, NativeViewAttached) {
   hosts_[1]->Detach();
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[0]->view(), hosts_[2]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[0]->view(), hosts_[2]->view()
+              ]]));
 
   hosts_[1]->AttachNativeView();
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view()
+              ]]));
 }
 
 // Tests that NativeViews order changes according to views::View hierarchy.
 TEST_F(NativeWidgetMacViewsOrderTest, ReorderViews) {
   native_host_parent_->ReorderChildView(hosts_[2]->host(), 1);
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[0]->view(), hosts_[2]->view(), hosts_[1]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[0]->view(), hosts_[2]->view(), hosts_[1]->view()
+              ]]));
 
   native_host_parent_->RemoveChildView(hosts_[2]->host());
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[0]->view(), hosts_[1]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[0]->view(), hosts_[1]->view()
+              ]]));
 
   View* new_parent = new View();
   native_host_parent_->RemoveChildView(hosts_[1]->host());
@@ -2272,11 +2282,15 @@
   new_parent->AddChildView(hosts_[1]->host());
   new_parent->AddChildView(hosts_[2]->host());
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view()
+              ]]));
 
   native_host_parent_->ReorderChildView(new_parent, 0);
   EXPECT_NSEQ([GetContentNativeView() subviews],
-              (@[ hosts_[1]->view(), hosts_[2]->view(), hosts_[0]->view() ]));
+              ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
+                hosts_[1]->view(), hosts_[2]->view(), hosts_[0]->view()
+              ]]));
 }
 
 // Test that unassociated native views stay on top after reordering.
@@ -2284,15 +2298,17 @@
   base::scoped_nsobject<NSView> child_view([[NSView alloc] init]);
   [GetContentNativeView() addSubview:child_view];
   EXPECT_NSEQ(
-      [GetContentNativeView() subviews], (@[
+      [GetContentNativeView() subviews],
+      ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
         hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view(), child_view
-      ]));
+      ]]));
 
   native_host_parent_->ReorderChildView(hosts_[2]->host(), 1);
   EXPECT_NSEQ(
-      [GetContentNativeView() subviews], (@[
+      [GetContentNativeView() subviews],
+      ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[
         hosts_[0]->view(), hosts_[2]->view(), hosts_[1]->view(), child_view
-      ]));
+      ]]));
 }
 
 // Test -[NSWindowDelegate windowShouldClose:].
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 0207ba2..45ef586d 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -2950,7 +2950,7 @@
 
   // Increment |touch_down_contexts_| on a pointer down. This variable
   // is used to debounce the WM_MOUSEACTIVATE events.
-  if (message == WM_POINTERDOWN) {
+  if (message == WM_POINTERDOWN || message == WM_NCPOINTERDOWN) {
     touch_down_contexts_++;
     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
@@ -3004,10 +3004,24 @@
     if (event_type == ui::ET_TOUCH_RELEASED)
       id_generator_.ReleaseNumber(pointer_id);
 
-    // Mark all touch released events handled. These will usually turn into tap
+    // Mark touch released events handled. These will usually turn into tap
     // gestures, and doing this avoids propagating the event to other windows.
-    const bool always_mark_handled = event_type == ui::ET_TOUCH_RELEASED;
-    SetMsgHandled(always_mark_handled || event.handled());
+    if (delegate_->GetFrameMode() == FrameMode::SYSTEM_DRAWN) {
+      // WM_NCPOINTERUP must be DefWindowProc'ed in order for the system caption
+      // buttons to work correctly.
+      if (message == WM_POINTERUP)
+        event.SetHandled();
+    } else {
+      // Messages on HTCAPTION should be DefWindowProc'ed, as we let Windows
+      // take care of dragging the window and double-tapping to maximize.
+      const bool on_titlebar =
+          SendMessage(hwnd(), WM_NCHITTEST, 0, l_param) == HTCAPTION;
+      // Unlike above, we must mark both WM_POINTERUP and WM_NCPOINTERUP as
+      // handled, in order for the custom caption buttons to work correctly.
+      if (event_type == ui::ET_TOUCH_RELEASED && !on_titlebar)
+        event.SetHandled();
+    }
+    SetMsgHandled(event.handled());
   }
   return 0;
 }
diff --git a/ui/views/win/hwnd_message_handler.h b/ui/views/win/hwnd_message_handler.h
index d76b006..8f28cdd 100644
--- a/ui/views/win/hwnd_message_handler.h
+++ b/ui/views/win/hwnd_message_handler.h
@@ -355,6 +355,9 @@
     CR_MESSAGE_HANDLER_EX(WM_POINTERUPDATE, OnPointerEvent)
     CR_MESSAGE_HANDLER_EX(WM_POINTERENTER, OnPointerEvent)
     CR_MESSAGE_HANDLER_EX(WM_POINTERLEAVE, OnPointerEvent)
+    CR_MESSAGE_HANDLER_EX(WM_NCPOINTERDOWN, OnPointerEvent)
+    CR_MESSAGE_HANDLER_EX(WM_NCPOINTERUP, OnPointerEvent)
+    CR_MESSAGE_HANDLER_EX(WM_NCPOINTERUPDATE, OnPointerEvent)
 
     // Key events.
     CR_MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyEvent)
diff --git a/ui/views/win/pen_event_processor.cc b/ui/views/win/pen_event_processor.cc
index e142993..67ee69fc 100644
--- a/ui/views/win/pen_event_processor.cc
+++ b/ui/views/win/pen_event_processor.cc
@@ -48,7 +48,7 @@
     DCHECK(!eraser_pointer_id_ || *eraser_pointer_id_ == mapped_pointer_id);
     eraser_pointer_id_ = mapped_pointer_id;
   } else if (eraser_pointer_id_ && *eraser_pointer_id_ == mapped_pointer_id &&
-             message == WM_POINTERUP) {
+             (message == WM_POINTERUP || message == WM_NCPOINTERUP)) {
     input_type = ui::EventPointerType::POINTER_TYPE_ERASER;
     eraser_pointer_id_.reset();
   }
@@ -113,6 +113,7 @@
   int click_count = 0;
   switch (message) {
     case WM_POINTERDOWN:
+    case WM_NCPOINTERDOWN:
       event_type = ui::ET_MOUSE_PRESSED;
       if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_DOWN)
         changed_flag = ui::EF_LEFT_MOUSE_BUTTON;
@@ -122,6 +123,7 @@
       sent_mouse_down_ = true;
       break;
     case WM_POINTERUP:
+    case WM_NCPOINTERUP:
       event_type = ui::ET_MOUSE_RELEASED;
       if (pointer_info.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) {
         flag |= ui::EF_LEFT_MOUSE_BUTTON;
@@ -137,6 +139,7 @@
       sent_mouse_down_ = false;
       break;
     case WM_POINTERUPDATE:
+    case WM_NCPOINTERUPDATE:
       event_type = ui::ET_MOUSE_DRAGGED;
       if (flag == ui::EF_NONE)
         event_type = ui::ET_MOUSE_MOVED;
@@ -169,10 +172,12 @@
   ui::EventType event_type = ui::ET_TOUCH_MOVED;
   switch (message) {
     case WM_POINTERDOWN:
+    case WM_NCPOINTERDOWN:
       event_type = ui::ET_TOUCH_PRESSED;
       sent_touch_start_ = true;
       break;
     case WM_POINTERUP:
+    case WM_NCPOINTERUP:
       event_type = ui::ET_TOUCH_RELEASED;
       id_generator_->ReleaseNumber(pointer_id);
       if (!sent_touch_start_)
@@ -180,6 +185,7 @@
       sent_touch_start_ = false;
       break;
     case WM_POINTERUPDATE:
+    case WM_NCPOINTERUPDATE:
       event_type = ui::ET_TOUCH_MOVED;
       break;
     default:
diff --git a/ui/views_bridge_mac/bridged_native_widget_impl.mm b/ui/views_bridge_mac/bridged_native_widget_impl.mm
index 0777853..1f368c2 100644
--- a/ui/views_bridge_mac/bridged_native_widget_impl.mm
+++ b/ui/views_bridge_mac/bridged_native_widget_impl.mm
@@ -44,6 +44,18 @@
 constexpr auto kUIPaintTimeout = base::TimeDelta::FromSeconds(5);
 }  // namespace
 
+// The NSView that hosts the composited CALayer drawing the UI. It fills the
+// window but is not hittable so that accessibility hit tests always go to the
+// BridgedContentView.
+@interface ViewsCompositorSuperview : NSView
+@end
+
+@implementation ViewsCompositorSuperview
+- (NSView*)hitTest:(NSPoint)aPoint {
+  return nil;
+}
+@end
+
 // Self-owning animation delegate that starts a hide animation, then calls
 // -[NSWindow close] when the animation ends, releasing itself.
 @interface ViewsNSWindowCloseAnimator : NSObject<NSAnimationDelegate> {
@@ -172,6 +184,9 @@
                                  void* rank_as_void) {
   DCHECK_NE(lhs, rhs);
 
+  if ([lhs isKindOfClass:[ViewsCompositorSuperview class]])
+    return NSOrderedAscending;
+
   const RankMap* rank = static_cast<const RankMap*>(rank_as_void);
   auto left_rank = rank->find(lhs);
   auto right_rank = rank->find(rhs);
@@ -486,13 +501,20 @@
   // this should be treated as an error and caught early.
   CHECK(bridged_view_);
 
-  // Set the layer first to create a layer-hosting view (not layer-backed), and
-  // set the compositor output to go to that layer.
-  base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
+  // Beware: This view was briefly removed (in favor of a bare CALayer) in
+  // crrev/c/1236675. The ordering of unassociated layers relative to NSView
+  // layers is undefined on macOS 10.12 and earlier, so the compositor layer
+  // ended up covering up subviews (see crbug/899499).
+  base::scoped_nsobject<NSView> compositor_view(
+      [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
+  [compositor_view
+      setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+  auto* background_layer = [CALayer layer];
   display_ca_layer_tree_ =
-      std::make_unique<ui::DisplayCALayerTree>(background_layer.get());
-  [bridged_view_ setLayer:background_layer];
-  [bridged_view_ setWantsLayer:YES];
+      std::make_unique<ui::DisplayCALayerTree>(background_layer);
+  [compositor_view setLayer:background_layer];
+  [compositor_view setWantsLayer:YES];
+  [bridged_view_ addSubview:compositor_view];
 
   [window_ setContentView:bridged_view_];
 }
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
index 1714455..f4e00640 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
@@ -160,7 +160,7 @@
         --cr-input-error-display: none;
         --cr-input-input: {
           font-size: 28px;
-          letter-spacing: 28px;
+          letter-spacing: 18px;
         };
         --cr-input-padding-bottom: 1px;
         --cr-input-padding-end: 0;