diff --git a/AUTHORS b/AUTHORS
index 8231fc7c..b153a16 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -809,6 +809,7 @@
 Shaobo Yan <shaobo.yan@intel.com>
 Shashi Kumar <sk.kumar@samsung.com>
 Shawn Anastasio <shawnanastasio@gmail.com>
+Shelley Vohr <shelley.vohr@gmail.com>
 Shen Yu <shenyu.tcv@gmail.com>
 Sherry Mou <wenjinm@amazon.com>
 Shez Baig <sbaig1@bloomberg.net>
diff --git a/BUILD.gn b/BUILD.gn
index e06cc8b..f50c9f9 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -207,7 +207,7 @@
       "//third_party/blink/renderer/platform/heap:blink_heap_unittests",
       "//third_party/catapult/telemetry:bitmaptools($host_toolchain)",
       "//third_party/smhasher:pmurhash",
-      "//tools/imagediff($host_toolchain)",
+      "//tools/imagediff",
       "//ui/display:display_unittests",
       "//ui/events:events_unittests",
       "//ui/latency:latency_unittests",
diff --git a/DEPS b/DEPS
index df7b97d..1b3c8e5 100644
--- a/DEPS
+++ b/DEPS
@@ -131,7 +131,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '67e3f02c0ad461789b2d5e74ad05743c6695e5e9',
+  'v8_revision': '96fc7ac109d13d86d67572349a5eec7f1540abdd',
   # 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.
@@ -139,11 +139,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '5de69e91bd939f8c2acc3ccab095d770bf378481',
+  'angle_revision': '5546fb4fdd85dbef7b87b8f3727aaca00966d8de',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '201f136c99079f03bd849da73df49c739d4c9c4c',
+  'swiftshader_revision': 'a1924739029e89d4f5d40b4acb5d2e2f10186ee0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -190,7 +190,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': '3c7b056c0c8bee3b6649194c55d083be1cb9c3ba',
+  'catapult_revision': '9950df105a7c461881c43cb1e20c427f4759f00f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -738,7 +738,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a0a1dcc9585331d677966a2b7ea09aab1b13f4fb',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '14a41b31642551e0ce4025fb6f3a55f6389a5d35',
       'condition': 'checkout_linux',
   },
 
@@ -763,7 +763,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'fe34723a55ec71b235512eac5ad561faf3151d6e',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '5117888302cfc6e8a6fc58de1148c18c15012317',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -798,7 +798,7 @@
   },
 
   'src/third_party/ffmpeg':
-    Var('chromium_git') + '/chromium/third_party/ffmpeg.git' + '@' + '41268576ad9a8b760287101f4f58d0ef468798af',
+    Var('chromium_git') + '/chromium/third_party/ffmpeg.git' + '@' + '7e1e8a4f7df474a4f8109c507a09621acad40314',
 
   'src/third_party/flac':
     Var('chromium_git') + '/chromium/deps/flac.git' + '@' + 'af862024c8c8fa0ae07ced05e89013d881b00596',
@@ -832,7 +832,7 @@
   },
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'ec484527b6438438cb4fe2ffa22fd1ebb3cb0378',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'd90d548161bc46341121b5c5bd77acb27ac1584f',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1276,7 +1276,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0f51b2e123f39c9ff12e621b0b47dd28dd64424',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '25fb765367e1ac7481d43cf846947eff327181de',
+    Var('webrtc_git') + '/src.git' + '@' + 'b8a4d688f90780f5cbc1a0250f926db648feb772',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1317,7 +1317,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@72565d358a645aa30087844e419463928d669351',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@fe5a160336aa7bb3ecff38c189c1c83a0bbc3709',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/app_list/views/contents_view.cc b/ash/app_list/views/contents_view.cc
index 80a119f..32c7f4b4 100644
--- a/ash/app_list/views/contents_view.cc
+++ b/ash/app_list/views/contents_view.cc
@@ -365,6 +365,9 @@
   gfx::Transform transform;
   transform.Scale(scale, scale);
   search_box->GetWidget()->GetNativeView()->SetTransform(transform);
+
+  for (auto& observer : search_box_observers_)
+    observer.OnSearchBoxBoundsUpdated();
 }
 
 void ContentsView::UpdateExpandArrowOpacity(double progress,
@@ -636,6 +639,16 @@
   expand_arrow_view_->SetVisible(show);
 }
 
+void ContentsView::AddSearchBoxUpdateObserver(
+    SearchBoxUpdateObserver* observer) {
+  search_box_observers_.AddObserver(observer);
+}
+
+void ContentsView::RemoveSearchBoxUpdateObserver(
+    SearchBoxUpdateObserver* observer) {
+  search_box_observers_.RemoveObserver(observer);
+}
+
 bool ContentsView::ShouldLayoutPage(AppListPage* page,
                                     ash::AppListState current_state,
                                     ash::AppListState target_state) const {
diff --git a/ash/app_list/views/contents_view.h b/ash/app_list/views/contents_view.h
index a736536..7e3df4b2 100644
--- a/ash/app_list/views/contents_view.h
+++ b/ash/app_list/views/contents_view.h
@@ -17,6 +17,8 @@
 #include "ash/app_list/pagination_model_observer.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "ui/views/view.h"
 #include "ui/views/view_model.h"
 
@@ -51,6 +53,13 @@
 class APP_LIST_EXPORT ContentsView : public views::View,
                                      public PaginationModelObserver {
  public:
+  // This class observes the search box Updates.
+  class SearchBoxUpdateObserver : public base::CheckedObserver {
+   public:
+    // Called when search box bounds is updated.
+    virtual void OnSearchBoxBoundsUpdated() = 0;
+  };
+
   explicit ContentsView(AppListView* app_list_view);
   ~ContentsView() override;
 
@@ -182,6 +191,9 @@
   // and tablet mode is enabled.
   void SetExpandArrowViewVisibility(bool show);
 
+  void AddSearchBoxUpdateObserver(SearchBoxUpdateObserver* observer);
+  void RemoveSearchBoxUpdateObserver(SearchBoxUpdateObserver* observer);
+
  private:
   // Sets the active launcher page, accounting for whether the change is for
   // search results.
@@ -271,6 +283,8 @@
   // Manages the pagination for the launcher pages.
   PaginationModel pagination_model_;
 
+  base::ObserverList<SearchBoxUpdateObserver> search_box_observers_;
+
   DISALLOW_COPY_AND_ASSIGN(ContentsView);
 };
 
diff --git a/ash/app_list/views/remove_query_confirmation_dialog.cc b/ash/app_list/views/remove_query_confirmation_dialog.cc
index 3f27dc75..846b2d0 100644
--- a/ash/app_list/views/remove_query_confirmation_dialog.cc
+++ b/ash/app_list/views/remove_query_confirmation_dialog.cc
@@ -4,6 +4,7 @@
 
 #include "ash/app_list/views/remove_query_confirmation_dialog.h"
 
+#include "ash/app_list/views/search_box_view.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/controls/label.h"
@@ -21,9 +22,11 @@
 
 RemoveQueryConfirmationDialog::RemoveQueryConfirmationDialog(
     RemovalConfirmationCallback confirm_callback,
-    int event_flags)
+    int event_flags,
+    ContentsView* contents_view)
     : confirm_callback_(std::move(confirm_callback)),
-      event_flags_(event_flags) {
+      event_flags_(event_flags),
+      contents_view_(contents_view) {
   const views::LayoutProvider* provider = views::LayoutProvider::Get();
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical,
@@ -36,23 +39,18 @@
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->SetAllowCharacterBreak(true);
   AddChildView(label);
+
+  contents_view_->AddSearchBoxUpdateObserver(this);
 }
 
-RemoveQueryConfirmationDialog::~RemoveQueryConfirmationDialog() {}
+RemoveQueryConfirmationDialog::~RemoveQueryConfirmationDialog() {
+  contents_view_->RemoveSearchBoxUpdateObserver(this);
+}
 
-void RemoveQueryConfirmationDialog::Show(gfx::NativeWindow parent,
-                                         const gfx::Rect& anchor_rect) {
+void RemoveQueryConfirmationDialog::Show(gfx::NativeWindow parent) {
   views::DialogDelegate::CreateDialogWidget(this, nullptr, parent);
-
-  views::Widget* widget = GetWidget();
-  DCHECK(widget);
-  gfx::Rect widget_rect = widget->GetWindowBoundsInScreen();
-  gfx::Point origin(anchor_rect.CenterPoint().x() - kDialogWidth / 2,
-                    anchor_rect.y() + kDialogYOffset);
-  widget_rect.set_origin(origin);
-  widget->SetBounds(widget_rect);
-
-  widget->Show();
+  UpdateBounds();
+  GetWidget()->Show();
 }
 
 base::string16 RemoveQueryConfirmationDialog::GetWindowTitle() const {
@@ -93,4 +91,22 @@
   return gfx::Size(default_width, GetHeightForWidth(default_width));
 }
 
+void RemoveQueryConfirmationDialog::OnSearchBoxBoundsUpdated() {
+  UpdateBounds();
+}
+
+void RemoveQueryConfirmationDialog::UpdateBounds() {
+  // Calculate confirmation dialog's origin in screen coordinates.
+  gfx::Rect anchor_rect =
+      contents_view_->GetSearchBoxView()->GetBoundsInScreen();
+  gfx::Point origin(anchor_rect.CenterPoint().x() - kDialogWidth / 2,
+                    anchor_rect.y() + kDialogYOffset);
+
+  views::Widget* widget = GetWidget();
+  DCHECK(widget);
+  gfx::Rect widget_rect = widget->GetWindowBoundsInScreen();
+  widget_rect.set_origin(origin);
+  widget->SetBounds(widget_rect);
+}
+
 }  // namespace app_list
diff --git a/ash/app_list/views/remove_query_confirmation_dialog.h b/ash/app_list/views/remove_query_confirmation_dialog.h
index 18328873f..3cbe4ff9 100644
--- a/ash/app_list/views/remove_query_confirmation_dialog.h
+++ b/ash/app_list/views/remove_query_confirmation_dialog.h
@@ -5,6 +5,7 @@
 #ifndef ASH_APP_LIST_VIEWS_REMOVE_QUERY_CONFIRMATION_DIALOG_H_
 #define ASH_APP_LIST_VIEWS_REMOVE_QUERY_CONFIRMATION_DIALOG_H_
 
+#include "ash/app_list/views/contents_view.h"
 #include "base/callback.h"
 #include "ui/views/window/dialog_delegate.h"
 
@@ -12,7 +13,9 @@
 
 // RemoveQueryConfirmationDialog displays the confirmation dialog for removing
 // a recent query suggestion.
-class RemoveQueryConfirmationDialog : public views::DialogDelegateView {
+class RemoveQueryConfirmationDialog
+    : public views::DialogDelegateView,
+      public ContentsView::SearchBoxUpdateObserver {
  public:
   // Callback to notify user's confirmation for removing the zero state
   // suggestion query. Invoked with true if user confirms removing query
@@ -22,11 +25,12 @@
   using RemovalConfirmationCallback = base::OnceCallback<void(bool, int)>;
 
   RemoveQueryConfirmationDialog(RemovalConfirmationCallback callback,
-                                int event_flgas);
+                                int event_flgas,
+                                ContentsView* contents_view);
   ~RemoveQueryConfirmationDialog() override;
 
-  // Shows the dialog with |parent| and |anchor_rect| in screen coordinates.
-  void Show(gfx::NativeWindow parent, const gfx::Rect& anchor_rect);
+  // Shows the dialog with |parent|.
+  void Show(gfx::NativeWindow parent);
 
  private:
   // views::WidgetDelegate:
@@ -42,8 +46,14 @@
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
 
+  // ContentsView::SearchBoxUpdateObserver
+  void OnSearchBoxBoundsUpdated() override;
+
+  void UpdateBounds();
+
   RemovalConfirmationCallback confirm_callback_;
   int event_flags_;
+  ContentsView* const contents_view_;  // Owned by the views hierarchy
 
   DISALLOW_COPY_AND_ASSIGN(RemoveQueryConfirmationDialog);
 };
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index fa1dcc0..de7082b 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -204,6 +204,10 @@
 }
 
 bool SearchResultPageView::IsFirstResultTile() const {
+  // In the event that the result does not exist, it is not a tile.
+  if (!first_result_view_ || !first_result_view_->result())
+    return false;
+
   // |kRecommendation| result type refers to tiles in Zero State.
   return first_result_view_->result()->display_type() ==
              ash::SearchResultDisplayType::kTile ||
diff --git a/ash/app_list/views/search_result_tile_item_list_view.cc b/ash/app_list/views/search_result_tile_item_list_view.cc
index ca6e01e..c4a9829 100644
--- a/ash/app_list/views/search_result_tile_item_list_view.cc
+++ b/ash/app_list/views/search_result_tile_item_list_view.cc
@@ -123,8 +123,10 @@
     }
 
     if (i >= display_results.size()) {
-      if (is_play_store_app_search_enabled_)
+      if (is_app_reinstall_recommendation_enabled_ ||
+          is_play_store_app_search_enabled_) {
         separator_views_[i]->SetVisible(false);
+      }
 
       tile_views_[i]->SetResult(nullptr);
       continue;
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc
index 1867b3f3..383c460 100644
--- a/ash/app_list/views/search_result_view.cc
+++ b/ash/app_list/views/search_result_view.cc
@@ -479,13 +479,9 @@
       RemoveQueryConfirmationDialog* dialog = new RemoveQueryConfirmationDialog(
           base::BindOnce(&SearchResultView::OnQueryRemovalAccepted,
                          weak_ptr_factory_.GetWeakPtr()),
-          event_flags);
+          event_flags, list_view_->app_list_main_view()->contents_view());
 
-      // Calculate confirmation dialog's origin in screen coordinates.
-      gfx::Rect search_box_rect = list_view_->app_list_main_view()
-                                      ->search_box_view()
-                                      ->GetBoundsInScreen();
-      dialog->Show(GetWidget()->GetNativeWindow(), search_box_rect);
+      dialog->Show(GetWidget()->GetNativeWindow());
     } else if (button_action ==
                ash::OmniBoxZeroStateAction::kAppendSuggestion) {
       RecordZeroStateSearchResultUserActionHistogram(
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index a66663e..2755c9c1 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1496,8 +1496,11 @@
       <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_WARNING" desc="Template for text shown in the public account user pod, informing the user that this is a public, managed account.">
         The device admin may monitor your browsing activity.
       </message>
-      <message name="IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_WARNING" desc="Text shown in the public account user pod, informing the user that this session is managed and admin can monitor all activity.">
-        The device administrator has access to all activity, including passwords and communications.
+      <message name="IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_FULL_WARNING" desc="Text shown in the public account user pod in case of risky configuration, informing the user that this session is managed and admin can monitor all activity.">
+        The device administrator may be able to monitor your activity.
+      </message>
+      <message name="IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_SOFT_WARNING" desc="Text shown in the public account user pod in case of non-risky configuration, informing the user that this session is managed and admin can monitor all activity.">
+        The device administrator may be able to monitor your activity.
       </message>
       <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_SIGNOUT_REMINDER" desc="Text shown in the public account user pod, reminding the user to log out.">
         Your internet session will be cleared when you sign out. <ph name="LEARN_MORE">$1<ex>Learn more</ex></ph>
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_FULL_WARNING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_FULL_WARNING.png.sha1
new file mode 100644
index 0000000..22300db
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_FULL_WARNING.png.sha1
@@ -0,0 +1 @@
+e4dbafdd6d3b8d693c0517c614e6ce3cf2884c4b
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_SOFT_WARNING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_SOFT_WARNING.png.sha1
new file mode 100644
index 0000000..6208ff3d
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_SOFT_WARNING.png.sha1
@@ -0,0 +1 @@
+980ed20bd0ed1748276b5ea307a0e3e841745dcc
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_WARNING.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_WARNING.png.sha1
deleted file mode 100644
index 9a0d5850..0000000
--- a/ash/ash_strings_grd/IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_WARNING.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5f0f4d14e51910e03eb4de622aeb2ad4e7658c7e
\ No newline at end of file
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 18b0695..4ad8aba 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -134,15 +134,15 @@
   DISALLOW_COPY_AND_ASSIGN(AuthErrorLearnMoreButton);
 };
 
-// Returns the first or last focusable child of |root|. If |reverse| is false,
-// this returns the first focusable child. If |reverse| is true, this returns
+// Focuses the first or last focusable child of |root|. If |reverse| is false,
+// this focuses the first focusable child. If |reverse| is true, this focuses
 // the last focusable child.
-views::View* FindFirstOrLastFocusableChild(views::View* root, bool reverse) {
+void FocusFirstOrLastFocusableChild(views::View* root, bool reverse) {
   views::FocusSearch search(root, reverse /*cycle*/,
                             false /*accessibility_mode*/);
   views::FocusTraversable* dummy_focus_traversable;
   views::View* dummy_focus_traversable_view;
-  return search.FindNextFocusableView(
+  views::View* focusable_view = search.FindNextFocusableView(
       root,
       reverse ? views::FocusSearch::SearchDirection::kBackwards
               : views::FocusSearch::SearchDirection::kForwards,
@@ -150,6 +150,8 @@
       views::FocusSearch::StartingViewPolicy::kSkipStartingView,
       views::FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog,
       &dummy_focus_traversable, &dummy_focus_traversable_view);
+  if (focusable_view)
+    focusable_view->RequestFocus();
 }
 
 // Make a section of the text bold.
@@ -1092,7 +1094,7 @@
   if (!reverse || lock_screen_apps_active_)
     FocusNextWidget(reverse);
   else
-    FindFirstOrLastFocusableChild(this, reverse)->RequestFocus();
+    FocusFirstOrLastFocusableChild(this, reverse);
 }
 
 void LockContentsView::OnOobeDialogStateChanged(mojom::OobeDialogState state) {
@@ -1108,7 +1110,7 @@
   // from the system shelf (or tray) - lock shelf view expect the focus to be
   // taken when it passes it to lock screen view, and can misbehave in case the
   // focus is kept in it.
-  FindFirstOrLastFocusableChild(this, reverse)->RequestFocus();
+  FocusFirstOrLastFocusableChild(this, reverse);
 
   if (lock_screen_apps_active_) {
     Shell::Get()->login_screen_controller()->FocusLockScreenApps(reverse);
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index a69b4fe..8d15f2f 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -2184,4 +2184,18 @@
   lock->AcceleratorPressed(ui::Accelerator(ui::VKEY_LEFT, 0));
 }
 
+TEST_F(LockContentsViewUnitTest, OnFocusLeavingSystemTrayWithNoUsers) {
+  auto* lock = new LockContentsView(
+      mojom::TrayActionState::kNotAvailable, LockScreen::ScreenType::kLock,
+      DataDispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+  SetWidget(CreateWidgetWithContent(lock));
+
+  // Check that there is always a focusable view after transitioning focus.
+  lock->OnFocusLeavingSystemTray(false /* reverse */);
+  EXPECT_TRUE(lock->GetFocusManager()->GetFocusedView());
+  lock->OnFocusLeavingSystemTray(true /* reverse */);
+  EXPECT_TRUE(lock->GetFocusManager()->GetFocusedView());
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index 87d7a6b..4121156d 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -243,11 +243,11 @@
     base::string16 label_text;
     if (warning_type == WarningType::kFullWarning) {
       label_text = l10n_util::GetStringUTF16(
-          IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_WARNING);
+          IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_FULL_WARNING);
       image_->SetVisible(true);
     } else if (warning_type == WarningType::kSoftWarning) {
       label_text = l10n_util::GetStringUTF16(
-          IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_WARNING);
+          IDS_ASH_LOGIN_MANAGED_SESSION_MONITORING_SOFT_WARNING);
       image_->SetVisible(false);
     }
     label_->SetText(label_text);
diff --git a/ash/public/interfaces/tablet_mode.mojom b/ash/public/interfaces/tablet_mode.mojom
index eec8ab9..0e98802 100644
--- a/ash/public/interfaces/tablet_mode.mojom
+++ b/ash/public/interfaces/tablet_mode.mojom
@@ -15,4 +15,7 @@
   // Sets a client (e.g. chrome). Triggers OnTabletModeToggled() to provide
   // the initial state.
   SetClient(TabletModeClient client);
+
+  // Enables or disables tablet mode. For testing only.
+  SetTabletModeEnabledForTesting(bool enabled) => (bool enabled);
 };
diff --git a/ash/shelf/shelf_background_animator_unittest.cc b/ash/shelf/shelf_background_animator_unittest.cc
index 22b2c91..4b3cf3b3 100644
--- a/ash/shelf/shelf_background_animator_unittest.cc
+++ b/ash/shelf/shelf_background_animator_unittest.cc
@@ -316,40 +316,15 @@
           ->background_animator_for_testing());
 
   NotifySessionStateChanged(session_manager::SessionState::LOGIN_PRIMARY);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
+  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);
 
   SimulateUserLogin("user1@test.com");
 
-  NotifySessionStateChanged(
-      session_manager::SessionState::LOGGED_IN_NOT_ACTIVE);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
-
   // The shelf has a non-transparent background only when session state is
   // active.
   NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
   EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
             GetBaseColor(kShelfDefaultBaseColor));
-
-  NotifySessionStateChanged(session_manager::SessionState::LOCKED);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
-
-  // Ensure the shelf background color is correct after unlocking.
-  NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(kShelfDefaultBaseColor));
-
-  NotifySessionStateChanged(session_manager::SessionState::LOGIN_SECONDARY);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
-
-  // Ensure the shelf background color is correct after closing the user adding
-  // screen.
-  NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(kShelfDefaultBaseColor));
 }
 
 // Verify the target colors of the shelf and item backgrounds are updated based
@@ -366,15 +341,13 @@
           ->background_animator_for_testing());
 
   NotifySessionStateChanged(session_manager::SessionState::OOBE);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
+  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);
 
   SimulateUserLogin("user1@test.com");
 
   NotifySessionStateChanged(
       session_manager::SessionState::LOGGED_IN_NOT_ACTIVE);
-  EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
-            GetBaseColor(SK_ColorTRANSPARENT));
+  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);
 
   NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
   EXPECT_EQ(GetBaseColor(test_api.shelf_background_target_color()),
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 02aaab9..9d0ba1fa 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -295,7 +295,8 @@
   // of the shelf.
   DCHECK(login_shelf_view_->visible());
   gfx::Rect login_view_button_bounds =
-      login_shelf_view_->get_button_union_bounds();
+      login_shelf_view_->ConvertRectToWidget(login_shelf_view_->GetMirroredRect(
+          login_shelf_view_->get_button_union_bounds()));
   aura::Window* source = login_shelf_view_->GetWidget()->GetNativeWindow();
   aura::Window::ConvertRectToTarget(source, target->parent(),
                                     &login_view_button_bounds);
diff --git a/ash/system/network/active_network_icon.cc b/ash/system/network/active_network_icon.cc
index 6499215..fb0d801f 100644
--- a/ash/system/network/active_network_icon.cc
+++ b/ash/system/network/active_network_icon.cc
@@ -16,12 +16,13 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 
-using chromeos::NetworkState;
 using chromeos::NetworkStateHandler;
 using chromeos::NetworkTypePattern;
 
 namespace ash {
 
+using network_icon::NetworkIconState;
+
 namespace {
 
 bool IsTrayIcon(network_icon::IconType icon_type) {
@@ -37,16 +38,18 @@
   return kUnifiedMenuIconColor;
 }
 
-const NetworkState* GetConnectingOrConnected(
-    const NetworkState* connecting_network,
-    const NetworkState* connected_network) {
+base::Optional<NetworkIconState> GetConnectingOrConnected(
+    const chromeos::NetworkState* connecting_network,
+    const chromeos::NetworkState* connected_network) {
   if (connecting_network &&
       (!connected_network || connecting_network->connect_requested())) {
     // If connecting to a network, and there is either no connected network or
     // the connection was user requested, use the connecting network.
-    return connecting_network;
+    return base::make_optional<NetworkIconState>(connecting_network);
   }
-  return connected_network;
+  if (connected_network)
+    return base::make_optional<NetworkIconState>(connected_network);
+  return base::nullopt;
 }
 
 }  // namespace
@@ -70,7 +73,7 @@
       return l10n_util::GetStringUTF16(cellular_uninitialized_msg_);
     return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
   }
-  return network_icon::GetLabelForNetwork(default_network_, icon_type);
+  return network_icon::GetLabelForNetwork(*default_network_, icon_type);
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetSingleImage(
@@ -80,8 +83,8 @@
   if (!default_network_ && cellular_uninitialized_msg_ != 0) {
     if (animating)
       *animating = true;
-    return network_icon::GetConnectingImageForNetworkType(shill::kTypeCellular,
-                                                          icon_type);
+    return network_icon::GetConnectingImageForNetworkType(
+        network_icon::NetworkType::kCellular, icon_type);
   }
   return GetDefaultImageImpl(default_network_, icon_type, animating);
 }
@@ -89,10 +92,9 @@
 gfx::ImageSkia ActiveNetworkIcon::GetDualImagePrimary(
     network_icon::IconType icon_type,
     bool* animating) {
-  const NetworkState* default_network = default_network_;
-  if (default_network &&
-      default_network->Matches(NetworkTypePattern::Cellular())) {
-    if (default_network->IsConnectedState()) {
+  if (default_network_ &&
+      default_network_->type == network_icon::NetworkType::kCellular) {
+    if (network_icon::IsConnected(*default_network_)) {
       // TODO: Show proper technology badges.
       if (animating)
         *animating = false;
@@ -100,9 +102,9 @@
                                    GetDefaultColorForIconType(icon_type));
     }
     // If Cellular is connecting, use the active non cellular network.
-    default_network = active_non_cellular_;
+    return GetDefaultImageImpl(active_non_cellular_, icon_type, animating);
   }
-  return GetDefaultImageImpl(default_network, icon_type, animating);
+  return GetDefaultImageImpl(default_network_, icon_type, animating);
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetDualImageCellular(
@@ -118,19 +120,19 @@
   if (cellular_uninitialized_msg_ != 0) {
     if (animating)
       *animating = true;
-    return network_icon::GetConnectingImageForNetworkType(shill::kTypeCellular,
-                                                          icon_type);
+    return network_icon::GetConnectingImageForNetworkType(
+        network_icon::NetworkType::kCellular, icon_type);
   }
 
   if (!active_cellular_) {
     if (animating)
       *animating = false;
     return network_icon::GetDisconnectedImageForNetworkType(
-        shill::kTypeCellular);
+        network_icon::NetworkType::kCellular);
   }
 
   return network_icon::GetImageForNonVirtualNetwork(
-      active_cellular_, icon_type, false /* show_vpn_badge */, animating);
+      *active_cellular_, icon_type, false /* show_vpn_badge */, animating);
 }
 
 void ActiveNetworkIcon::InitForTesting(
@@ -155,31 +157,37 @@
 }
 
 gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageImpl(
-    const NetworkState* default_network,
+    const base::Optional<NetworkIconState>& default_network,
     network_icon::IconType icon_type,
     bool* animating) {
-  if (!default_network_)
+  if (!default_network) {
+    VLOG(1) << __func__ << ": No network";
     return GetDefaultImageForNoNetwork(icon_type, animating);
+  }
   // Don't show connected Ethernet in the tray unless a VPN is present.
-  if (default_network->Matches(NetworkTypePattern::Ethernet()) &&
+  if (default_network->type == network_icon::NetworkType::kEthernet &&
       IsTrayIcon(icon_type) && !active_vpn_) {
     if (animating)
       *animating = false;
+    VLOG(1) << __func__ << ": Ethernet: No icon";
     return gfx::ImageSkia();
   }
 
   // Connected network with a connecting VPN.
-  if (default_network->IsConnectedState() && active_vpn_ &&
-      active_vpn_->IsConnectingState()) {
+  if (network_icon::IsConnected(*default_network) && active_vpn_ &&
+      network_icon::IsConnecting(*active_vpn_)) {
     if (animating)
       *animating = true;
+    VLOG(1) << __func__ << ": Connected with connecting VPN";
     return network_icon::GetConnectedNetworkWithConnectingVpnImage(
-        default_network, icon_type);
+        *default_network, icon_type);
   }
 
   // Default behavior: connected or connecting network, possibly with VPN badge.
   bool show_vpn_badge = !!active_vpn_;
-  return network_icon::GetImageForNonVirtualNetwork(default_network, icon_type,
+  VLOG(1) << __func__ << ": Network: " << default_network->name
+          << " Strength: " << default_network->signal_strength;
+  return network_icon::GetImageForNonVirtualNetwork(*default_network, icon_type,
                                                     show_vpn_badge, animating);
 }
 
@@ -191,8 +199,8 @@
   if (network_state_handler_ &&
       network_state_handler_->IsTechnologyEnabled(NetworkTypePattern::WiFi())) {
     // WiFi is enabled but disconnected, show an empty wedge.
-    return network_icon::GetBasicImage(icon_type, shill::kTypeWifi,
-                                       false /* connected */);
+    return network_icon::GetBasicImage(
+        icon_type, network_icon::NetworkType::kWiFi, false /* connected */);
   }
   // WiFi is disabled, show a full icon with a strikethrough.
   return network_icon::GetImageForWiFiEnabledState(false /* not enabled*/,
@@ -200,7 +208,7 @@
 }
 
 void ActiveNetworkIcon::UpdateActiveNetworks() {
-  std::vector<const NetworkState*> active_networks;
+  std::vector<const chromeos::NetworkState*> active_networks;
   network_state_handler_->GetActiveNetworkListByType(
       NetworkTypePattern::Default(), &active_networks);
   ActiveNetworksChanged(active_networks);
@@ -241,23 +249,23 @@
 }
 
 void ActiveNetworkIcon::ActiveNetworksChanged(
-    const std::vector<const NetworkState*>& active_networks) {
-  active_cellular_ = nullptr;
-  active_vpn_ = nullptr;
+    const std::vector<const chromeos::NetworkState*>& active_networks) {
+  active_cellular_.reset();
+  active_vpn_.reset();
 
-  const NetworkState* connected_network = nullptr;
-  const NetworkState* connected_non_cellular = nullptr;
-  const NetworkState* connecting_network = nullptr;
-  const NetworkState* connecting_non_cellular = nullptr;
-  for (const NetworkState* network : active_networks) {
+  const chromeos::NetworkState* connected_network = nullptr;
+  const chromeos::NetworkState* connected_non_cellular = nullptr;
+  const chromeos::NetworkState* connecting_network = nullptr;
+  const chromeos::NetworkState* connecting_non_cellular = nullptr;
+  for (const chromeos::NetworkState* network : active_networks) {
     if (network->Matches(NetworkTypePattern::VPN())) {
       if (!active_vpn_)
-        active_vpn_ = network;
+        active_vpn_ = base::make_optional<NetworkIconState>(network);
       continue;
     }
     if (network->Matches(NetworkTypePattern::Cellular())) {
       if (!active_cellular_)
-        active_cellular_ = network;
+        active_cellular_ = base::make_optional<NetworkIconState>(network);
     }
     if (network->IsConnectedState()) {
       if (!connected_network)
@@ -279,8 +287,21 @@
     }
   }
 
+  VLOG_IF(2, connected_network)
+      << __func__ << ": Connected network: " << connected_network->name()
+      << " State: " << connected_network->connection_state()
+      << " Strength: " << connected_network->signal_strength();
+  VLOG_IF(2, connecting_network)
+      << __func__ << ": Connecting network: " << connecting_network->name()
+      << " State: " << connecting_network->connection_state()
+      << " Strength: " << connecting_network->signal_strength();
+
   default_network_ =
       GetConnectingOrConnected(connecting_network, connected_network);
+  VLOG_IF(2, default_network_)
+      << __func__ << ": Default network: " << default_network_->name
+      << " Strength: " << default_network_->signal_strength;
+
   active_non_cellular_ =
       GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
 
diff --git a/ash/system/network/active_network_icon.h b/ash/system/network/active_network_icon.h
index 79422c5e..f4937f61 100644
--- a/ash/system/network/active_network_icon.h
+++ b/ash/system/network/active_network_icon.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "ash/system/network/network_icon.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "chromeos/network/network_state_handler_observer.h"
@@ -69,7 +70,7 @@
   void ShutdownNetworkStateHandler();
 
   gfx::ImageSkia GetDefaultImageImpl(
-      const chromeos::NetworkState* default_network,
+      const base::Optional<network_icon::NetworkIconState>& default_network,
       network_icon::IconType icon_type,
       bool* animating);
 
@@ -89,10 +90,10 @@
   void OnShuttingDown() override;
 
   chromeos::NetworkStateHandler* network_state_handler_ = nullptr;
-  const chromeos::NetworkState* default_network_ = nullptr;
-  const chromeos::NetworkState* active_non_cellular_ = nullptr;
-  const chromeos::NetworkState* active_cellular_ = nullptr;
-  const chromeos::NetworkState* active_vpn_ = nullptr;
+  base::Optional<network_icon::NetworkIconState> default_network_;
+  base::Optional<network_icon::NetworkIconState> active_non_cellular_;
+  base::Optional<network_icon::NetworkIconState> active_cellular_;
+  base::Optional<network_icon::NetworkIconState> active_vpn_;
   int cellular_uninitialized_msg_ = 0;
   base::Time uninitialized_state_time_;
 
diff --git a/ash/system/network/active_network_icon_unittest.cc b/ash/system/network/active_network_icon_unittest.cc
index 97f126f..ac251235 100644
--- a/ash/system/network/active_network_icon_unittest.cc
+++ b/ash/system/network/active_network_icon_unittest.cc
@@ -76,7 +76,6 @@
                        base::Value(state));
     SetServiceProperty(cellular_path_, shill::kSignalStrengthProperty,
                        base::Value(100));
-    base::RunLoop().RunUntilIdle();
   }
 
   void SetCellularUninitialized(bool scanning) {
@@ -99,7 +98,8 @@
 
   gfx::ImageSkia ImageForNetwork(const chromeos::NetworkState* network) {
     return network_icon::GetImageForNonVirtualNetwork(
-        network, icon_type_, false /* show_vpn_badge */);
+        network_icon::NetworkIconState(network), icon_type_,
+        false /* show_vpn_badge */);
   }
 
   bool AreImagesEqual(const gfx::ImageSkia& image,
@@ -122,6 +122,9 @@
       const std::string& connection_state,
       int signal_strength = 100) {
     std::string id = base::StringPrintf("reference_%d", reference_count_++);
+    VLOG(1) << "CreateStandaloneNetworkState: " << id << " type: " << type
+            << " State: " << connection_state
+            << " Strength: " << signal_strength;
     return helper_.CreateStandaloneNetworkState(id, type, connection_state,
                                                 signal_strength);
   }
diff --git a/ash/system/network/network_icon.cc b/ash/system/network/network_icon.cc
index 148512c4..ed226c0 100644
--- a/ash/system/network/network_icon.cc
+++ b/ash/system/network/network_icon.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/network/network_icon.h"
 
+#include <utility>
+
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/network_icon_image_source.h"
 #include "ash/resources/vector_icons/vector_icons.h"
@@ -16,9 +18,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/network/network_state.h"
 #include "chromeos/network/network_type_pattern.h"
+#include "chromeos/network/onc/onc_translation_tables.h"
 #include "chromeos/network/tether_constants.h"
+#include "components/onc/onc_constants.h"
 #include "components/vector_icons/vector_icons.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/image/image_skia_source.h"
@@ -26,7 +29,6 @@
 #include "ui/gfx/skia_util.h"
 #include "ui/gfx/vector_icon_types.h"
 
-using chromeos::NetworkState;
 using chromeos::NetworkTypePattern;
 
 namespace ash {
@@ -37,42 +39,38 @@
 // class used for maintaining a map of network state and images.
 class NetworkIconImpl {
  public:
-  NetworkIconImpl(const std::string& path,
+  NetworkIconImpl(const std::string& guid,
                   IconType icon_type,
-                  const std::string& network_type);
+                  NetworkType network_type);
 
   // Determines whether or not the associated network might be dirty and if so
   // updates and generates the icon. Does nothing if network no longer exists.
-  void Update(const chromeos::NetworkState* network, bool show_vpn_badge);
+  void Update(const NetworkIconState& network, bool show_vpn_badge);
 
   const gfx::ImageSkia& image() const { return image_; }
 
  private:
   // Updates |strength_index_| for wireless networks. Returns true if changed.
-  bool UpdateWirelessStrengthIndex(const chromeos::NetworkState* network);
+  bool UpdateWirelessStrengthIndex(const NetworkIconState& network);
 
   // Updates the local state for cellular networks. Returns true if changed.
-  bool UpdateCellularState(const chromeos::NetworkState* network);
+  bool UpdateCellularState(const NetworkIconState& network);
 
   // Gets |badges| based on |network| and the current state.
-  void GetBadges(const NetworkState* network, Badges* badges);
+  void GetBadges(const NetworkIconState& network, Badges* badges);
 
   // Gets the appropriate icon and badges and composites the image.
-  void GenerateImage(const chromeos::NetworkState* network);
-
-  // Network path, used for debugging.
-  std::string network_path_;
+  void GenerateImage(const NetworkIconState& network);
 
   // Defines color theme and VPN badging
   const IconType icon_type_;
 
   // Cached state of the network when the icon was last generated.
-  std::string connection_state_;
+  ConnectionStateType connection_state_;
   int strength_index_ = -1;
   Badge technology_badge_ = {};
   bool show_vpn_badge_ = false;
   bool is_roaming_ = false;
-  bool behind_captive_portal_ = false;
 
   // Generated icon image.
   gfx::ImageSkia image_;
@@ -108,14 +106,14 @@
 }
 
 void PurgeIconMap(IconType icon_type,
-                  const std::set<std::string>& network_paths) {
+                  const std::set<std::string>& network_guids) {
   NetworkIconMap* icon_map = GetIconMapInstance(icon_type, false);
   if (!icon_map)
     return;
   for (NetworkIconMap::iterator loop_iter = icon_map->begin();
        loop_iter != icon_map->end();) {
     NetworkIconMap::iterator cur_iter = loop_iter++;
-    if (network_paths.count(cur_iter->first) == 0) {
+    if (network_guids.count(cur_iter->first) == 0) {
       delete cur_iter->second;
       icon_map->erase(cur_iter);
     }
@@ -166,30 +164,16 @@
 //------------------------------------------------------------------------------
 // Utilities for extracting icon images.
 
-ImageType ImageTypeForNetworkType(const std::string& type) {
-  if (NetworkTypePattern::WiFi().MatchesType(type))
+ImageType ImageTypeForNetworkType(NetworkType network_type) {
+  if (network_type == NetworkType::kWiFi)
     return ARCS;
 
-  if (NetworkTypePattern::Mobile().MatchesType(type))
+  if (network_type == NetworkType::kCellular ||
+      network_type == NetworkType::kTether) {
     return BARS;
-
-  return NONE;
-}
-
-// Returns the network type, performing a check to see if Wi-Fi networks
-// have an associated Tether network. Used to display the correct icon.
-std::string GetEffectiveNetworkType(const NetworkState* network,
-                                    IconType icon_type) {
-  if (IsTrayIcon(icon_type) && network->type() == shill::kTypeWifi &&
-      !network->tether_guid().empty()) {
-    return chromeos::kTypeTether;
   }
 
-  return network->type();
-}
-
-ImageType ImageTypeForNetwork(const NetworkState* network, IconType icon_type) {
-  return ImageTypeForNetworkType(GetEffectiveNetworkType(network, icon_type));
+  return NONE;
 }
 
 gfx::Size GetSizeForIconType(IconType icon_type) {
@@ -266,28 +250,28 @@
   return zero_based_index + 1;
 }
 
-Badge BadgeForNetworkTechnology(const NetworkState* network,
+Badge BadgeForNetworkTechnology(const NetworkIconState& network,
                                 IconType icon_type) {
   Badge badge = {nullptr, GetDefaultColorForIconType(icon_type)};
-  const std::string& technology = network->network_technology();
-  if (technology == shill::kNetworkTechnologyEvdo) {
+  const std::string& technology = network.network_technology;
+  if (technology == onc::cellular::kTechnologyEvdo) {
     badge.icon = &kNetworkBadgeTechnologyEvdoIcon;
-  } else if (technology == shill::kNetworkTechnology1Xrtt) {
+  } else if (technology == onc::cellular::kTechnologyCdma1Xrtt) {
     badge.icon = &kNetworkBadgeTechnology1xIcon;
-  } else if (technology == shill::kNetworkTechnologyGprs ||
-             technology == shill::kNetworkTechnologyGsm) {
+  } else if (technology == onc::cellular::kTechnologyGprs ||
+             technology == onc::cellular::kTechnologyGsm) {
     badge.icon = &kNetworkBadgeTechnologyGprsIcon;
-  } else if (technology == shill::kNetworkTechnologyEdge) {
+  } else if (technology == onc::cellular::kTechnologyEdge) {
     badge.icon = &kNetworkBadgeTechnologyEdgeIcon;
-  } else if (technology == shill::kNetworkTechnologyUmts) {
+  } else if (technology == onc::cellular::kTechnologyUmts) {
     badge.icon = &kNetworkBadgeTechnology3gIcon;
-  } else if (technology == shill::kNetworkTechnologyHspa) {
+  } else if (technology == onc::cellular::kTechnologyHspa) {
     badge.icon = &kNetworkBadgeTechnologyHspaIcon;
-  } else if (technology == shill::kNetworkTechnologyHspaPlus) {
+  } else if (technology == onc::cellular::kTechnologyHspaPlus) {
     badge.icon = &kNetworkBadgeTechnologyHspaPlusIcon;
-  } else if (technology == shill::kNetworkTechnologyLte) {
+  } else if (technology == onc::cellular::kTechnologyLte) {
     badge.icon = &kNetworkBadgeTechnologyLteIcon;
-  } else if (technology == shill::kNetworkTechnologyLteAdvanced) {
+  } else if (technology == onc::cellular::kTechnologyLteAdvanced) {
     badge.icon = &kNetworkBadgeTechnologyLteAdvancedIcon;
   } else {
     return {};
@@ -295,25 +279,20 @@
   return badge;
 }
 
-gfx::ImageSkia GetIcon(const NetworkState* network,
+gfx::ImageSkia GetIcon(const NetworkIconState& network,
                        IconType icon_type,
                        int strength_index) {
-  if (network->Matches(NetworkTypePattern::Ethernet())) {
+  if (network.type == NetworkType::kEthernet) {
     return gfx::CreateVectorIcon(vector_icons::kEthernetIcon,
                                  GetDefaultColorForIconType(icon_type));
   }
-  if (network->Matches(NetworkTypePattern::Wireless())) {
-    return GetImageForIndex(ImageTypeForNetwork(network, icon_type), icon_type,
-                            strength_index);
-  }
-  if (network->Matches(NetworkTypePattern::VPN())) {
+  if (network.type == NetworkType::kVPN) {
     DCHECK(!IsTrayIcon(icon_type));
     return gfx::CreateVectorIcon(kNetworkVpnIcon,
                                  GetDefaultColorForIconType(ICON_TYPE_LIST));
   }
-
-  NOTREACHED() << "Request for icon for unsupported type: " << network->type();
-  return gfx::ImageSkia();
+  return GetImageForIndex(ImageTypeForNetworkType(network.type), icon_type,
+                          strength_index);
 }
 
 gfx::ImageSkia GetConnectingVpnImage(IconType icon_type) {
@@ -324,41 +303,95 @@
 
 }  // namespace
 
+NetworkIconState::NetworkIconState(const chromeos::NetworkState* network) {
+  guid = network->guid();
+  name = network->name();
+
+  const std::string& network_type = network->type();
+  if (NetworkTypePattern::Cellular().MatchesType(network_type) ||
+      NetworkTypePattern::Wimax().MatchesType(network_type)) {
+    type = NetworkType::kCellular;
+  } else if (NetworkTypePattern::Ethernet().MatchesType(network_type)) {
+    type = NetworkType::kEthernet;
+  } else if (NetworkTypePattern::Tether().MatchesType(network_type)) {
+    type = NetworkType::kTether;
+  } else if (NetworkTypePattern::VPN().MatchesType(network_type)) {
+    type = NetworkType::kVPN;
+  } else {
+    type = NetworkType::kWiFi;
+  }
+
+  if (network->IsCaptivePortal()) {
+    connection_state = ConnectionStateType::kPortal;
+  } else if (network->IsConnectedState()) {
+    connection_state = ConnectionStateType::kConnected;
+  } else if (network->IsConnectingState()) {
+    connection_state = ConnectionStateType::kConnecting;
+  } else {
+    connection_state = ConnectionStateType::kNotConnected;
+  }
+
+  if (type == NetworkType::kWiFi && !network->security_class().empty()) {
+    chromeos::onc::TranslateStringToONC(chromeos::onc::kWiFiSecurityTable,
+                                        network->security_class(), &security);
+  }
+  if (type == NetworkType::kCellular) {
+    if (!network->network_technology().empty()) {
+      chromeos::onc::TranslateStringToONC(
+          chromeos::onc::kNetworkTechnologyTable, network->network_technology(),
+          &network_technology);
+    }
+    if (!network->activation_state().empty()) {
+      chromeos::onc::TranslateStringToONC(chromeos::onc::kActivationStateTable,
+                                          network->activation_state(),
+                                          &activation_state);
+    }
+  }
+  signal_strength = network->signal_strength();
+  is_roaming = network->IndicateRoaming();
+}
+
+NetworkIconState::NetworkIconState(const NetworkIconState& other) = default;
+
+NetworkIconState& NetworkIconState::operator=(const NetworkIconState& other) =
+    default;
+
+NetworkIconState::~NetworkIconState() = default;
+
 //------------------------------------------------------------------------------
 // NetworkIconImpl
 
-NetworkIconImpl::NetworkIconImpl(const std::string& path,
+NetworkIconImpl::NetworkIconImpl(const std::string& guid,
                                  IconType icon_type,
-                                 const std::string& network_type)
-    : network_path_(path), icon_type_(icon_type) {
+                                 NetworkType network_type)
+    : icon_type_(icon_type) {
   // Default image is null.
 }
 
-void NetworkIconImpl::Update(const NetworkState* network, bool show_vpn_badge) {
-  DCHECK(network);
+void NetworkIconImpl::Update(const NetworkIconState& network,
+                             bool show_vpn_badge) {
   // Determine whether or not we need to update the icon.
   bool dirty = image_.isNull();
 
-  std::string connection_state = network->connection_state();
-  if (connection_state != connection_state_) {
-    connection_state_ = connection_state;
+  if (network.connection_state != connection_state_) {
+    VLOG(2) << "Update connection state: "
+            << static_cast<int>(network.connection_state);
+    connection_state_ = network.connection_state;
     dirty = true;
   }
 
-  bool behind_captive_portal = network->IsCaptivePortal();
-  if (behind_captive_portal != behind_captive_portal_) {
-    behind_captive_portal_ = behind_captive_portal;
-    dirty = true;
-  }
-
-  if (network->Matches(NetworkTypePattern::Wireless()))
+  NetworkType type = network.type;
+  if (type == NetworkType::kCellular || type == NetworkType::kTether ||
+      type == NetworkType::kWiFi) {
     dirty |= UpdateWirelessStrengthIndex(network);
+  }
 
-  if (network->Matches(NetworkTypePattern::Cellular()))
+  if (type == NetworkType::kCellular)
     dirty |= UpdateCellularState(network);
 
   bool new_show_vpn_badge = show_vpn_badge && IconTypeHasVPNBadge(icon_type_);
   if (new_show_vpn_badge != show_vpn_badge_) {
+    VLOG(2) << "Update VPN badge: " << new_show_vpn_badge;
     show_vpn_badge_ = new_show_vpn_badge;
     dirty = true;
   }
@@ -369,61 +402,61 @@
   }
 }
 
-bool NetworkIconImpl::UpdateWirelessStrengthIndex(const NetworkState* network) {
-  int index = StrengthIndex(network->signal_strength());
+bool NetworkIconImpl::UpdateWirelessStrengthIndex(
+    const NetworkIconState& network) {
+  int index = StrengthIndex(network.signal_strength);
   if (index != strength_index_) {
+    VLOG(2) << "New strength index: " << index;
     strength_index_ = index;
     return true;
   }
   return false;
 }
 
-bool NetworkIconImpl::UpdateCellularState(const NetworkState* network) {
+bool NetworkIconImpl::UpdateCellularState(const NetworkIconState& network) {
   bool dirty = false;
   if (!features::IsSeparateNetworkIconsEnabled()) {
     const Badge technology_badge =
         BadgeForNetworkTechnology(network, icon_type_);
     if (technology_badge != technology_badge_) {
+      VLOG(2) << "New technology badge.";
       technology_badge_ = technology_badge;
       dirty = true;
     }
   }
-  bool is_roaming = network->IndicateRoaming();
-  if (is_roaming != is_roaming_) {
-    is_roaming_ = is_roaming;
+
+  if (network.is_roaming != is_roaming_) {
+    VLOG(2) << "New is_roaming: " << network.is_roaming;
+    is_roaming_ = network.is_roaming;
     dirty = true;
   }
   return dirty;
 }
 
-void NetworkIconImpl::GetBadges(const NetworkState* network, Badges* badges) {
-  DCHECK(network);
-
-  const std::string& type = network->type();
+void NetworkIconImpl::GetBadges(const NetworkIconState& network,
+                                Badges* badges) {
+  const NetworkType type = network.type;
   const SkColor icon_color = GetDefaultColorForIconType(icon_type_);
-  if (type == shill::kTypeWifi) {
-    if (network->security_class() != shill::kSecurityNone &&
+  if (type == NetworkType::kWiFi) {
+    if (network.security != onc::wifi::kSecurityNone &&
         !IsTrayIcon(icon_type_)) {
       badges->bottom_right = {&kUnifiedNetworkBadgeSecureIcon, icon_color};
     }
-  } else if (type == shill::kTypeWimax) {
-    technology_badge_ = {&kNetworkBadgeTechnology4gIcon, icon_color};
-  } else if (type == shill::kTypeCellular) {
+  } else if (type == NetworkType::kCellular) {
     // technology_badge_ is set in UpdateCellularState.
-    if (network->IsConnectedState() && network->IndicateRoaming())
+    if (IsConnected(network) && network.is_roaming)
       badges->bottom_right = {&kNetworkBadgeRoamingIcon, icon_color};
   }
   // Only show technology badge when connected.
-  if (network->IsConnectedState() && !features::IsSeparateNetworkIconsEnabled())
+  if (IsConnected(network) && !features::IsSeparateNetworkIconsEnabled())
     badges->top_left = technology_badge_;
   if (show_vpn_badge_)
     badges->bottom_left = {&kUnifiedNetworkBadgeVpnIcon, icon_color};
-  if (behind_captive_portal_)
+  if (connection_state_ == ConnectionStateType::kPortal)
     badges->bottom_right = {&kUnifiedNetworkBadgeCaptivePortalIcon, icon_color};
 }
 
-void NetworkIconImpl::GenerateImage(const NetworkState* network) {
-  DCHECK(network);
+void NetworkIconImpl::GenerateImage(const NetworkIconState& network) {
   gfx::ImageSkia icon = GetIcon(network, icon_type_, strength_index_);
   Badges badges;
   GetBadges(network, &badges);
@@ -432,18 +465,19 @@
 
 namespace {
 
-NetworkIconImpl* FindAndUpdateImageImpl(const NetworkState* network,
+NetworkIconImpl* FindAndUpdateImageImpl(const NetworkIconState& network,
                                         IconType icon_type,
                                         bool show_vpn_badge) {
   // Find or add the icon.
   NetworkIconMap* icon_map = GetIconMap(icon_type);
   NetworkIconImpl* icon;
-  NetworkIconMap::iterator iter = icon_map->find(network->path());
+  NetworkIconMap::iterator iter = icon_map->find(network.guid);
   if (iter == icon_map->end()) {
-    icon = new NetworkIconImpl(network->path(), icon_type,
-                               GetEffectiveNetworkType(network, icon_type));
-    icon_map->insert(std::make_pair(network->path(), icon));
+    VLOG(1) << "new NetworkIconImpl: " << network.name;
+    icon = new NetworkIconImpl(network.guid, icon_type, network.type);
+    icon_map->insert(std::make_pair(network.guid, icon));
   } else {
+    VLOG(1) << "found NetworkIconImpl: " << network.name;
     icon = iter->second;
   }
 
@@ -457,29 +491,31 @@
 //------------------------------------------------------------------------------
 // Public interface
 
+bool IsConnected(const NetworkIconState& icon_state) {
+  return icon_state.connection_state == ConnectionStateType::kConnected ||
+         icon_state.connection_state == ConnectionStateType::kPortal;
+}
+
+bool IsConnecting(const NetworkIconState& icon_state) {
+  return icon_state.connection_state == ConnectionStateType::kConnecting;
+}
+
 const gfx::ImageSkia GetBasicImage(IconType icon_type,
-                                   const std::string& network_type,
+                                   NetworkType network_type,
                                    bool connected) {
-  DCHECK_NE(shill::kTypeVPN, network_type);
+  DCHECK_NE(NetworkType::kVPN, network_type);
   return GetImageForIndex(ImageTypeForNetworkType(network_type), icon_type,
                           connected ? kNumNetworkImages - 1 : 0);
 }
 
-gfx::ImageSkia GetImageForNonVirtualNetwork(const NetworkState* network,
+gfx::ImageSkia GetImageForNonVirtualNetwork(const NetworkIconState& network,
                                             IconType icon_type,
                                             bool show_vpn_badge,
                                             bool* animating) {
-  DCHECK(network);
-  DCHECK(!network->Matches(NetworkTypePattern::VPN()));
-  const std::string network_type = GetEffectiveNetworkType(network, icon_type);
+  DCHECK_NE(NetworkType::kVPN, network.type);
+  NetworkType network_type = network.type;
 
-  if (!network->visible()) {
-    if (animating)
-      *animating = false;
-    return GetBasicImage(icon_type, network_type, false /* connected */);
-  }
-
-  if (network->IsConnectingState()) {
+  if (IsConnecting(network)) {
     if (animating)
       *animating = true;
     return GetConnectingImageForNetworkType(network_type, icon_type);
@@ -492,12 +528,11 @@
   return icon->image();
 }
 
-gfx::ImageSkia GetImageForVPN(const NetworkState* vpn,
+gfx::ImageSkia GetImageForVPN(const NetworkIconState& vpn,
                               IconType icon_type,
                               bool* animating) {
-  DCHECK(vpn);
-  DCHECK(vpn->Matches(NetworkTypePattern::VPN()));
-  if (vpn->IsConnectingState()) {
+  DCHECK_EQ(NetworkType::kVPN, vpn.type);
+  if (IsConnecting(vpn)) {
     if (animating)
       *animating = true;
     return GetConnectingVpnImage(icon_type);
@@ -518,7 +553,7 @@
   }
 
   gfx::ImageSkia image =
-      GetBasicImage(icon_type, shill::kTypeWifi, true /* connected */);
+      GetBasicImage(icon_type, NetworkType::kWiFi, true /* connected */);
   Badges badges;
   if (!enabled) {
     badges.center = {&kNetworkBadgeOffIcon,
@@ -527,9 +562,9 @@
   return CreateNetworkIconImage(image, badges);
 }
 
-gfx::ImageSkia GetConnectingImageForNetworkType(const std::string& network_type,
+gfx::ImageSkia GetConnectingImageForNetworkType(NetworkType network_type,
                                                 IconType icon_type) {
-  DCHECK(network_type != shill::kTypeVPN);
+  DCHECK_NE(NetworkType::kVPN, network_type);
   ImageType image_type = ImageTypeForNetworkType(network_type);
   double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
 
@@ -538,9 +573,8 @@
 }
 
 gfx::ImageSkia GetConnectedNetworkWithConnectingVpnImage(
-    const NetworkState* connected_network,
+    const NetworkIconState& connected_network,
     IconType icon_type) {
-  DCHECK(connected_network);
   gfx::ImageSkia icon = GetImageForNonVirtualNetwork(
       connected_network, icon_type, false /* show_vpn_badge */);
   double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
@@ -551,8 +585,7 @@
   return CreateNetworkIconImage(icon, badges);
 }
 
-gfx::ImageSkia GetDisconnectedImageForNetworkType(
-    const std::string& network_type) {
+gfx::ImageSkia GetDisconnectedImageForNetworkType(NetworkType network_type) {
   return GetBasicImage(ICON_TYPE_LIST, network_type, false /* connected */);
 }
 
@@ -560,66 +593,63 @@
                                          SkColor badge_color) {
   gfx::ImageSkia icon =
       gfx::CanvasImageSource::MakeImageSkia<SignalStrengthImageSource>(
-          ImageTypeForNetworkType(shill::kTypeWifi), icon_color,
+          ImageTypeForNetworkType(NetworkType::kWiFi), icon_color,
           GetSizeForIconType(ICON_TYPE_LIST), kNumNetworkImages - 1);
   Badges badges;
   badges.bottom_right = {&kNetworkBadgeAddOtherIcon, badge_color};
   return CreateNetworkIconImage(icon, badges);
 }
 
-base::string16 GetLabelForNetwork(const chromeos::NetworkState* network,
+base::string16 GetLabelForNetwork(const NetworkIconState& network,
                                   IconType icon_type) {
-  DCHECK(network);
-  std::string activation_state = network->activation_state();
+  std::string activation_state = network.activation_state;
   if (icon_type == ICON_TYPE_LIST || icon_type == ICON_TYPE_MENU_LIST) {
     // Show "<network>: [Connecting|Activating]..."
-    if (icon_type != ICON_TYPE_MENU_LIST && network->IsConnectingState()) {
+    if (icon_type != ICON_TYPE_MENU_LIST && IsConnecting(network)) {
       return l10n_util::GetStringFUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_LIST_CONNECTING,
-          base::UTF8ToUTF16(network->name()));
+          base::UTF8ToUTF16(network.name));
     }
-    if (activation_state == shill::kActivationStateActivating) {
+    if (activation_state == onc::cellular::kActivating) {
       return l10n_util::GetStringFUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATING,
-          base::UTF8ToUTF16(network->name()));
+          base::UTF8ToUTF16(network.name));
     }
     // Show "Activate <network>" in list view only.
-    if (activation_state == shill::kActivationStateNotActivated ||
-        activation_state == shill::kActivationStatePartiallyActivated) {
+    if (activation_state == onc::cellular::kNotActivated ||
+        activation_state == onc::cellular::kPartiallyActivated) {
       return l10n_util::GetStringFUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATE,
-          base::UTF8ToUTF16(network->name()));
+          base::UTF8ToUTF16(network.name));
     }
   } else {
     // Show "[Connected to|Connecting to|Activating] <network>" (non-list view).
-    if (network->IsConnectedState()) {
+    if (IsConnected(network)) {
       return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED,
-                                        base::UTF8ToUTF16(network->name()));
+                                        base::UTF8ToUTF16(network.name));
     }
-    if (network->IsConnectingState()) {
+    if (IsConnecting(network)) {
       return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING,
-                                        base::UTF8ToUTF16(network->name()));
+                                        base::UTF8ToUTF16(network.name));
     }
-    if (activation_state == shill::kActivationStateActivating) {
+    if (activation_state == onc::cellular::kActivating) {
       return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING,
-                                        base::UTF8ToUTF16(network->name()));
+                                        base::UTF8ToUTF16(network.name));
     }
   }
 
   // Otherwise just show the network name or 'Ethernet'.
-  if (network->Matches(NetworkTypePattern::Ethernet())) {
+  if (network.type == NetworkType::kEthernet)
     return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET);
-  } else {
-    return base::UTF8ToUTF16(network->name());
-  }
+  return base::UTF8ToUTF16(network.name);
 }
 
-void PurgeNetworkIconCache(const std::set<std::string>& network_paths) {
-  PurgeIconMap(ICON_TYPE_TRAY_OOBE, network_paths);
-  PurgeIconMap(ICON_TYPE_TRAY_REGULAR, network_paths);
-  PurgeIconMap(ICON_TYPE_DEFAULT_VIEW, network_paths);
-  PurgeIconMap(ICON_TYPE_LIST, network_paths);
-  PurgeIconMap(ICON_TYPE_MENU_LIST, network_paths);
+void PurgeNetworkIconCache(const std::set<std::string>& network_guids) {
+  PurgeIconMap(ICON_TYPE_TRAY_OOBE, network_guids);
+  PurgeIconMap(ICON_TYPE_TRAY_REGULAR, network_guids);
+  PurgeIconMap(ICON_TYPE_DEFAULT_VIEW, network_guids);
+  PurgeIconMap(ICON_TYPE_LIST, network_guids);
+  PurgeIconMap(ICON_TYPE_MENU_LIST, network_guids);
 }
 
 SignalStrength GetSignalStrengthForNetwork(
diff --git a/ash/system/network/network_icon.h b/ash/system/network/network_icon.h
index 8a3e589..65d7e410 100644
--- a/ash/system/network/network_icon.h
+++ b/ash/system/network/network_icon.h
@@ -21,6 +21,42 @@
 namespace ash {
 namespace network_icon {
 
+// TODO(stevenjb): Replace with mojo enum once available.
+enum class NetworkType : int32_t {
+  kCellular,
+  kEthernet,
+  kTether,
+  kVPN,
+  kWiFi,
+};
+
+// TODO(stevenjb): Replace with mojo enum once available.
+enum class ConnectionStateType : int32_t {
+  kNotConnected,
+  kConnecting,
+  kConnected,
+  kPortal,
+};
+
+// TODO(stevenjb): Replace with mojo type once available.
+struct ASH_EXPORT NetworkIconState {
+  // Constructs a NetworkIconState from a NetworkState.
+  explicit NetworkIconState(const chromeos::NetworkState* network);
+  NetworkIconState(const NetworkIconState& other);
+  NetworkIconState& operator=(const NetworkIconState& other);
+  ~NetworkIconState();
+
+  std::string guid;
+  std::string name;
+  NetworkType type;
+  ConnectionStateType connection_state;
+  std::string security;            // ONC security type
+  std::string network_technology;  // ONC network technology type
+  std::string activation_state;    // ONC activation state
+  int signal_strength = 0;         // 0-100.
+  bool is_roaming = false;
+};
+
 // Type of icon which dictates color theme and VPN badging
 enum IconType {
   ICON_TYPE_TRAY_OOBE,     // dark icons with VPN badges, used during OOBE
@@ -33,10 +69,16 @@
 // Strength of a wireless signal.
 enum class SignalStrength { NONE, WEAK, MEDIUM, STRONG, NOT_WIRELESS };
 
+// Returns true if |icon_state| is connected or portal.
+bool IsConnected(const NetworkIconState& icon_state);
+
+// Returns true if |icon_state| is connecting.
+bool IsConnecting(const NetworkIconState& icon_state);
+
 // Returns an image to represent either a fully connected network or a
 // disconnected network.
 const gfx::ImageSkia GetBasicImage(IconType icon_type,
-                                   const std::string& network_type,
+                                   NetworkType network_type,
                                    bool connected);
 
 // Returns and caches an image for non VPN |network| which must not be null.
@@ -46,14 +88,14 @@
 // |animating| is an optional out parameter that is set to true when the
 // returned image should be animated.
 ASH_EXPORT gfx::ImageSkia GetImageForNonVirtualNetwork(
-    const chromeos::NetworkState* network,
+    const NetworkIconState& network,
     IconType icon_type,
     bool badge_vpn,
     bool* animating = nullptr);
 
 // Similar to above but for displaying only VPN icons, e.g. for the VPN menu
 // or Settings section.
-ASH_EXPORT gfx::ImageSkia GetImageForVPN(const chromeos::NetworkState* vpn,
+ASH_EXPORT gfx::ImageSkia GetImageForVPN(const NetworkIconState& vpn,
                                          IconType icon_type,
                                          bool* animating = nullptr);
 
@@ -64,18 +106,17 @@
     IconType = ICON_TYPE_DEFAULT_VIEW);
 
 // Returns the connecting image for a shill network non-VPN type.
-gfx::ImageSkia GetConnectingImageForNetworkType(const std::string& network_type,
+gfx::ImageSkia GetConnectingImageForNetworkType(NetworkType network_type,
                                                 IconType icon_type);
 
 // Returns the connected image for |connected_network| and |network_type| with a
 // connecting VPN badge.
 gfx::ImageSkia GetConnectedNetworkWithConnectingVpnImage(
-    const chromeos::NetworkState* connected_network,
+    const NetworkIconState& connected_network,
     IconType icon_type);
 
 // Returns the disconnected image for a shill network type.
-gfx::ImageSkia GetDisconnectedImageForNetworkType(
-    const std::string& network_type);
+gfx::ImageSkia GetDisconnectedImageForNetworkType(NetworkType network_type);
 
 // Returns the full strength image for a Wi-Fi network using |icon_color| for
 // the main icon and |badge_color| for the badge.
@@ -84,14 +125,13 @@
 
 // Returns the label for |network| based on |icon_type|. |network| cannot be
 // nullptr.
-ASH_EXPORT base::string16 GetLabelForNetwork(
-    const chromeos::NetworkState* network,
-    IconType icon_type);
+ASH_EXPORT base::string16 GetLabelForNetwork(const NetworkIconState&,
+                                             IconType icon_type);
 
-// Called periodically with the current list of network paths. Removes cached
+// Called periodically with the current list of network guids. Removes cached
 // entries that are no longer in the list.
 ASH_EXPORT void PurgeNetworkIconCache(
-    const std::set<std::string>& network_paths);
+    const std::set<std::string>& network_guids);
 
 // Called by ChromeVox to give a verbal indication of the network icon. Returns
 // the signal strength of |network|, if it is a network type with a signal
diff --git a/ash/system/network/network_icon_purger.cc b/ash/system/network/network_icon_purger.cc
index c6e45b5..44c6b7ad 100644
--- a/ash/system/network/network_icon_purger.cc
+++ b/ash/system/network/network_icon_purger.cc
@@ -21,12 +21,12 @@
   NetworkStateHandler::NetworkStateList networks;
   NetworkHandler::Get()->network_state_handler()->GetVisibleNetworkList(
       &networks);
-  std::set<std::string> network_paths;
+  std::set<std::string> network_guids;
   for (NetworkStateHandler::NetworkStateList::iterator iter = networks.begin();
        iter != networks.end(); ++iter) {
-    network_paths.insert((*iter)->path());
+    network_guids.insert((*iter)->guid());
   }
-  network_icon::PurgeNetworkIconCache(network_paths);
+  network_icon::PurgeNetworkIconCache(network_guids);
 }
 
 }  // namespace
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index 76304dc..4227034 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -96,10 +96,14 @@
     return network;
   }
 
+  gfx::Image GetImageForNonVirtualNetwork(const chromeos::NetworkState* network,
+                                          bool badge_vpn) {
+    return gfx::Image(network_icon::GetImageForNonVirtualNetwork(
+        network_icon::NetworkIconState(network), icon_type_, badge_vpn));
+  }
+
   gfx::Image ImageForNetwork(const chromeos::NetworkState* network) {
-    gfx::ImageSkia image_skia = GetImageForNonVirtualNetwork(
-        network, icon_type_, false /* show_vpn_badge */);
-    return gfx::Image(image_skia);
+    return GetImageForNonVirtualNetwork(network, false /* show_vpn_badge */);
   }
 
   void GetDefaultNetworkImageAndLabel(IconType icon_type,
@@ -118,8 +122,7 @@
   void GetAndCompareImagesByNetworkType(
       const chromeos::NetworkState* wifi_network,
       const chromeos::NetworkState* cellular_network,
-      const chromeos::NetworkState* tether_network,
-      const chromeos::NetworkState* wifi_tether_network) {
+      const chromeos::NetworkState* tether_network) {
     ASSERT_EQ(wifi_network->type(), shill::kTypeWifi);
     gfx::Image wifi_image = ImageForNetwork(wifi_network);
 
@@ -129,15 +132,9 @@
     ASSERT_EQ(tether_network->type(), chromeos::kTypeTether);
     gfx::Image tether_image = ImageForNetwork(tether_network);
 
-    ASSERT_EQ(wifi_tether_network->type(), shill::kTypeWifi);
-    ASSERT_FALSE(wifi_tether_network->tether_guid().empty());
-    gfx::Image wifi_tether_image = ImageForNetwork(wifi_tether_network);
-
     EXPECT_FALSE(gfx::test::AreImagesEqual(tether_image, wifi_image));
     EXPECT_FALSE(gfx::test::AreImagesEqual(cellular_image, wifi_image));
     EXPECT_TRUE(gfx::test::AreImagesEqual(tether_image, cellular_image));
-
-    EXPECT_TRUE(gfx::test::AreImagesEqual(tether_image, wifi_tether_image));
   }
 
   void SetCellularUnavailable() {
@@ -201,13 +198,8 @@
       CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
                                    shill::kStateIdle, 50);
 
-  std::unique_ptr<chromeos::NetworkState> wifi_tether_network =
-      CreateStandaloneWifiTetherNetworkState("wifi_tether", "tether",
-                                             shill::kStateIdle, 50);
-
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
-                                   tether_network.get(),
-                                   wifi_tether_network.get());
+                                   tether_network.get());
 }
 
 TEST_F(NetworkIconTest, CompareImagesByNetworkType_Connecting) {
@@ -223,13 +215,8 @@
       CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
                                    shill::kStateAssociation, 50);
 
-  std::unique_ptr<chromeos::NetworkState> wifi_tether_network =
-      CreateStandaloneWifiTetherNetworkState("wifi_tether", "tether",
-                                             shill::kStateAssociation, 50);
-
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
-                                   tether_network.get(),
-                                   wifi_tether_network.get());
+                                   tether_network.get());
 }
 
 TEST_F(NetworkIconTest, CompareImagesByNetworkType_Connected) {
@@ -245,13 +232,8 @@
       CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
                                    shill::kStateOnline, 50);
 
-  std::unique_ptr<chromeos::NetworkState> wifi_tether_network =
-      CreateStandaloneWifiTetherNetworkState("wifi_tether", "tether",
-                                             shill::kStateOnline, 50);
-
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
-                                   tether_network.get(),
-                                   wifi_tether_network.get());
+                                   tether_network.get());
 }
 
 TEST_F(NetworkIconTest, NetworkSignalStrength) {
@@ -656,10 +638,10 @@
   std::unique_ptr<chromeos::NetworkState> reference_eth =
       CreateStandaloneNetworkState("reference_eth", shill::kTypeEthernet,
                                    shill::kStateOnline, 0);
-  gfx::Image reference_eth_unbadged = gfx::Image(GetImageForNonVirtualNetwork(
-      reference_eth.get(), icon_type_, false /* show_vpn_badge */));
-  gfx::Image reference_eth_badged = gfx::Image(GetImageForNonVirtualNetwork(
-      reference_eth.get(), icon_type_, true /* show_vpn_badge */));
+  gfx::Image reference_eth_unbadged = GetImageForNonVirtualNetwork(
+      reference_eth.get(), false /* show_vpn_badge */);
+  gfx::Image reference_eth_badged = GetImageForNonVirtualNetwork(
+      reference_eth.get(), true /* show_vpn_badge */);
 
   EXPECT_FALSE(gfx::test::AreImagesEqual(gfx::Image(default_image),
                                          reference_eth_unbadged));
@@ -677,8 +659,8 @@
   std::unique_ptr<chromeos::NetworkState> reference_wifi =
       CreateStandaloneNetworkState("reference_wifi", shill::kTypeWifi,
                                    shill::kStateOnline, 45);
-  gfx::Image reference_wifi_badged = gfx::Image(GetImageForNonVirtualNetwork(
-      reference_wifi.get(), icon_type_, true /* show_vpn_badge */));
+  gfx::Image reference_wifi_badged = GetImageForNonVirtualNetwork(
+      reference_wifi.get(), true /* show_vpn_badge */);
   EXPECT_TRUE(gfx::test::AreImagesEqual(gfx::Image(default_image),
                                         reference_wifi_badged));
 
diff --git a/ash/system/network/network_list.cc b/ash/system/network/network_list.cc
index 87d47d4..7638c5f6 100644
--- a/ash/system/network/network_list.cc
+++ b/ash/system/network/network_list.cc
@@ -122,11 +122,13 @@
     if (!network)
       continue;
     bool prohibited_by_policy = network->blocked_by_policy();
+    network_icon::NetworkIconState network_icon_state(network);
     info->label = network_icon::GetLabelForNetwork(
-        network, network_icon::ICON_TYPE_MENU_LIST);
+        network_icon_state, network_icon::ICON_TYPE_MENU_LIST);
     // |network_list_| only contains non virtual networks.
     info->image = network_icon::GetImageForNonVirtualNetwork(
-        network, network_icon::ICON_TYPE_LIST, false /* badge_vpn */);
+        network_icon_state, network_icon::ICON_TYPE_LIST,
+        false /* badge_vpn */);
     info->disable =
         (network->activation_state() == shill::kActivationStateActivating) ||
         prohibited_by_policy;
diff --git a/ash/system/network/vpn_list_view.cc b/ash/system/network/vpn_list_view.cc
index a2c4813..16f86358 100644
--- a/ash/system/network/vpn_list_view.cc
+++ b/ash/system/network/vpn_list_view.cc
@@ -204,10 +204,11 @@
   Reset();
   disconnect_button_ = nullptr;
 
-  gfx::ImageSkia image =
-      network_icon::GetImageForVPN(vpn, network_icon::ICON_TYPE_LIST);
-  base::string16 label =
-      network_icon::GetLabelForNetwork(vpn, network_icon::ICON_TYPE_MENU_LIST);
+  network_icon::NetworkIconState vpn_icon_state(vpn);
+  gfx::ImageSkia image = network_icon::GetImageForVPN(
+      vpn_icon_state, network_icon::ICON_TYPE_LIST);
+  base::string16 label = network_icon::GetLabelForNetwork(
+      vpn_icon_state, network_icon::ICON_TYPE_MENU_LIST);
   AddIconAndLabel(image, label);
   if (vpn->IsConnectedState()) {
     owner_->SetupConnectedScrollListItem(this);
diff --git a/ash/wallpaper/wallpaper_controller.cc b/ash/wallpaper/wallpaper_controller.cc
index d2b1063..0d9c3e4 100644
--- a/ash/wallpaper/wallpaper_controller.cc
+++ b/ash/wallpaper/wallpaper_controller.cc
@@ -27,6 +27,7 @@
 #include "ash/wallpaper/wallpaper_window_state_manager.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/files/file_enumerator.h"
@@ -717,8 +718,15 @@
 }
 
 bool WallpaperController::ShouldApplyDimming() const {
-  return Shell::Get()->session_controller()->IsUserSessionBlocked() &&
-         !IsOneShotWallpaper();
+  // Dim the wallpaper in a blocked user session or in tablet mode unless during
+  // wallpaper preview.
+  const bool should_dim =
+      Shell::Get()->session_controller()->IsUserSessionBlocked() ||
+      (Shell::Get()
+           ->tablet_mode_controller()
+           ->IsTabletModeWindowManagerEnabled() &&
+       !confirm_preview_wallpaper_callback_);
+  return should_dim && !IsOneShotWallpaper();
 }
 
 bool WallpaperController::IsBlurAllowed() const {
@@ -1378,6 +1386,14 @@
   }
 }
 
+void WallpaperController::OnShellInitialized() {
+  Shell::Get()->tablet_mode_controller()->AddObserver(this);
+}
+
+void WallpaperController::OnShellDestroying() {
+  Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
+}
+
 void WallpaperController::OnWallpaperResized() {
   CalculateWallpaperColors();
   compositor_lock_.reset();
@@ -1423,6 +1439,14 @@
     MoveToLockedContainer();
 }
 
+void WallpaperController::OnTabletModeStarted() {
+  RepaintWallpaper();
+}
+
+void WallpaperController::OnTabletModeEnded() {
+  RepaintWallpaper();
+}
+
 void WallpaperController::CompositorLockTimedOut() {
   compositor_lock_.reset();
 }
@@ -1482,10 +1506,13 @@
   if (is_wallpaper_blurred) {
     blur = session_blocked ? login_constants::kBlurSigma : kWallpaperBlurSigma;
   }
-  RootWindowController::ForWindow(root_window)
-      ->wallpaper_widget_controller()
-      ->SetWallpaperWidget(CreateWallpaperWidget(root_window, container_id),
-                           blur);
+  WallpaperView* wallpaper_view = nullptr;
+  auto* wallpaper_widget_controller =
+      RootWindowController::ForWindow(root_window)
+          ->wallpaper_widget_controller();
+  auto* widget =
+      CreateWallpaperWidget(root_window, container_id, &wallpaper_view);
+  wallpaper_widget_controller->SetWallpaperWidget(widget, wallpaper_view, blur);
 }
 
 void WallpaperController::InstallDesktopControllerForAllWindows() {
@@ -2159,4 +2186,13 @@
       this, kCompositorLockTimeout);
 }
 
+void WallpaperController::RepaintWallpaper() {
+  for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
+    auto* wallpaper_view =
+        root_window_controller->wallpaper_widget_controller()->wallpaper_view();
+    if (wallpaper_view)
+      wallpaper_view->SchedulePaint();
+  }
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller.h b/ash/wallpaper/wallpaper_controller.h
index 039cf5f..9b49616 100644
--- a/ash/wallpaper/wallpaper_controller.h
+++ b/ash/wallpaper/wallpaper_controller.h
@@ -20,6 +20,7 @@
 #include "ash/wallpaper/wallpaper_info.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_color_calculator_observer.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_resizer_observer.h"
+#include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
@@ -68,6 +69,7 @@
                                        public WallpaperResizerObserver,
                                        public WallpaperColorCalculatorObserver,
                                        public SessionObserver,
+                                       public TabletModeObserver,
                                        public ui::CompositorLockClient {
  public:
   enum WallpaperResolution {
@@ -294,6 +296,8 @@
   // ShellObserver:
   void OnRootWindowAdded(aura::Window* root_window) override;
   void OnLocalStatePrefServiceInitialized(PrefService* pref_service) override;
+  void OnShellInitialized() override;
+  void OnShellDestroying() override;
 
   // WallpaperResizerObserver:
   void OnWallpaperResized() override;
@@ -304,6 +308,10 @@
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
 
+  // TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
+
   // CompositorLockClient:
   void CompositorLockTimedOut() override;
 
@@ -548,6 +556,10 @@
   // simplicity, we only lock the compositor for the internal display.
   void GetInternalDisplayCompositorLock();
 
+  // Schedules paint on all WallpaperViews owned by WallpaperWidgetControllers.
+  // This is used when we want to change wallpaper dimming.
+  void RepaintWallpaper();
+
   bool locked_;
 
   WallpaperMode wallpaper_mode_;
diff --git a/ash/wallpaper/wallpaper_view.cc b/ash/wallpaper/wallpaper_view.cc
index d40ed09..7d0cf4c 100644
--- a/ash/wallpaper/wallpaper_view.cc
+++ b/ash/wallpaper/wallpaper_view.cc
@@ -12,6 +12,7 @@
 #include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
@@ -31,7 +32,7 @@
 
 // The value used for alpha to apply a dark filter to the wallpaper in tablet
 // mode. A higher number up to 255 results in a darker wallpaper.
-constexpr int kWallpaperDimnessInTabletMode = 102;
+constexpr int kTabletModeWallpaperAlpha = 102;
 
 // A view that controls the child view's layer so that the layer always has the
 // same size as the display's original, un-scaled size in DIP. The layer is then
@@ -84,11 +85,15 @@
       SkColorSetA(login_constants::kDefaultBaseColor,
                   login_constants::kTranslucentColorDarkenAlpha),
       SkColorSetA(darken_color, 0xFF));
-  return SkColorSetA(darken_color, login_constants::kTranslucentAlpha);
-}
 
-SkColor GetWallpaperDarkenColorForTabletMode() {
-  return SkColorSetA(GetWallpaperDarkenColor(), kWallpaperDimnessInTabletMode);
+  int alpha = login_constants::kTranslucentAlpha;
+  if (Shell::Get()
+          ->tablet_mode_controller()
+          ->IsTabletModeWindowManagerEnabled()) {
+    alpha = kTabletModeWallpaperAlpha;
+  }
+
+  return SkColorSetA(darken_color, alpha);
 }
 
 }  // namespace
@@ -98,31 +103,10 @@
 
 WallpaperView::WallpaperView() {
   set_context_menu_controller(this);
-  tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller());
-  is_tablet_mode_ = Shell::Get()
-                        ->tablet_mode_controller()
-                        ->IsTabletModeWindowManagerEnabled();
 }
 
 WallpaperView::~WallpaperView() = default;
 
-void WallpaperView::OnTabletModeStarted() {
-  is_tablet_mode_ = true;
-  SchedulePaint();
-}
-
-void WallpaperView::OnTabletModeEnded() {
-  is_tablet_mode_ = false;
-  SchedulePaint();
-}
-
-void WallpaperView::OnTabletControllerDestroyed() {
-  tablet_mode_observer_.RemoveAll();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// WallpaperView, views::View overrides:
-
 void WallpaperView::OnPaint(gfx::Canvas* canvas) {
   // Scale the image while maintaining the aspect ratio, cropping as necessary
   // to fill the wallpaper. Ideally the image should be larger than the largest
@@ -143,9 +127,6 @@
   if (controller->ShouldApplyDimming()) {
     flags.setColorFilter(SkColorFilter::MakeModeFilter(
         GetWallpaperDarkenColor(), SkBlendMode::kDarken));
-  } else if (is_tablet_mode_) {
-    flags.setColorFilter(SkColorFilter::MakeModeFilter(
-        GetWallpaperDarkenColorForTabletMode(), SkBlendMode::kDarken));
   }
 
   switch (layout) {
@@ -220,7 +201,8 @@
 }
 
 views::Widget* CreateWallpaperWidget(aura::Window* root_window,
-                                     int container_id) {
+                                     int container_id,
+                                     WallpaperView** out_wallpaper_view) {
   WallpaperController* controller = Shell::Get()->wallpaper_controller();
 
   views::Widget* wallpaper_widget = new views::Widget;
@@ -233,6 +215,7 @@
   wallpaper_widget->Init(params);
   WallpaperView* wallpaper_view = new WallpaperView();  // Owned by views.
   wallpaper_widget->SetContentsView(new LayerControlView(wallpaper_view));
+  *out_wallpaper_view = wallpaper_view;
   int animation_type =
       controller->ShouldShowInitialAnimation()
           ? wm::WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE
diff --git a/ash/wallpaper/wallpaper_view.h b/ash/wallpaper/wallpaper_view.h
index 52abdf19..dcd14a0 100644
--- a/ash/wallpaper/wallpaper_view.h
+++ b/ash/wallpaper/wallpaper_view.h
@@ -5,11 +5,6 @@
 #ifndef ASH_WALLPAPER_WALLPAPER_VIEW_H_
 #define ASH_WALLPAPER_WALLPAPER_VIEW_H_
 
-#include <memory>
-
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_observer.h"
-#include "base/scoped_observer.h"
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/view.h"
 
@@ -19,9 +14,7 @@
 
 namespace ash {
 
-class WallpaperView : public views::View,
-                      public views::ContextMenuController,
-                      TabletModeObserver {
+class WallpaperView : public views::View, public views::ContextMenuController {
  public:
   WallpaperView();
   ~WallpaperView() override;
@@ -33,25 +26,17 @@
   void OnPaint(gfx::Canvas* canvas) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
 
-  // Overridden from TabletModeObserver:
-  void OnTabletModeStarted() override;
-  void OnTabletModeEnded() override;
-  void OnTabletControllerDestroyed() override;
-
   // Overridden from views::ContextMenuController:
   void ShowContextMenuForView(views::View* source,
                               const gfx::Point& point,
                               ui::MenuSourceType source_type) override;
 
-  ScopedObserver<TabletModeController, TabletModeObserver>
-      tablet_mode_observer_{this};
-  bool is_tablet_mode_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(WallpaperView);
 };
 
 views::Widget* CreateWallpaperWidget(aura::Window* root_window,
-                                     int container_id);
+                                     int container_id,
+                                     WallpaperView** out_wallpaper_view);
 
 }  // namespace ash
 
diff --git a/ash/wallpaper/wallpaper_widget_controller.cc b/ash/wallpaper/wallpaper_widget_controller.cc
index 7db9175..d2e0eb5 100644
--- a/ash/wallpaper/wallpaper_widget_controller.cc
+++ b/ash/wallpaper/wallpaper_widget_controller.cc
@@ -13,6 +13,7 @@
 #include "base/scoped_observer.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_observer.h"
+#include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/views/widget/widget.h"
@@ -191,8 +192,10 @@
   animation_end_callbacks_.emplace_back(std::move(callback));
 }
 
-void WallpaperWidgetController::SetWallpaperWidget(views::Widget* widget,
-                                                   float blur_sigma) {
+void WallpaperWidgetController::SetWallpaperWidget(
+    views::Widget* widget,
+    WallpaperView* wallpaper_view,
+    float blur_sigma) {
   DCHECK(widget);
 
   // If there is a widget currently being shown, finish the animation and set it
@@ -205,6 +208,8 @@
   animating_widget_ = std::make_unique<WidgetHandler>(this, widget);
   animating_widget_->SetBlur(blur_sigma);
   animating_widget_->Show();
+
+  wallpaper_view_ = wallpaper_view;
 }
 
 bool WallpaperWidgetController::Reparent(aura::Window* root_window,
@@ -228,6 +233,12 @@
   return active_widget_ ? active_widget_->blur_sigma() : 0.f;
 }
 
+ui::Layer* WallpaperWidgetController::GetLayer() {
+  if (GetAnimatingWidget())
+    return GetAnimatingWidget()->GetNativeWindow()->layer();
+  return GetWidget() ? GetWidget()->GetNativeWindow()->layer() : nullptr;
+}
+
 void WallpaperWidgetController::ResetWidgetsForTesting() {
   animating_widget_.reset();
   active_widget_.reset();
diff --git a/ash/wallpaper/wallpaper_widget_controller.h b/ash/wallpaper/wallpaper_widget_controller.h
index c95d652c..6d4ee134 100644
--- a/ash/wallpaper/wallpaper_widget_controller.h
+++ b/ash/wallpaper/wallpaper_widget_controller.h
@@ -16,11 +16,16 @@
 class Window;
 }
 
+namespace ui {
+class Layer;
+}
+
 namespace views {
 class Widget;
 }
 
 namespace ash {
+class WallpaperView;
 
 // This class manages widget-based wallpapers.
 // WallpaperWidgetController is owned by RootWindowController.
@@ -52,7 +57,9 @@
   // |animating_widget_|).
   // |blur_sigma| - if non-zero, the blur that should be applied to the
   //     wallpaper widget layer.
-  void SetWallpaperWidget(views::Widget* widget, float blur_sigma);
+  void SetWallpaperWidget(views::Widget* widget,
+                          WallpaperView* wallpaper_view,
+                          float blur_sigma);
 
   // Move the wallpaper for |root_window| to the specified |container|.
   // The lock screen moves the wallpaper container to hides the user's windows.
@@ -65,6 +72,14 @@
   // Returns the blur sigma applied on the wallpaper layer.
   float GetWallpaperBlur() const;
 
+  // Gets the layer associated with |animating_widget_| if that exists. If not
+  // then gets the layer associated with |active_widget_|.
+  ui::Layer* GetLayer();
+
+  // TODO: Get the wallpaper view from |animating_widget_| or |active_widget_|
+  // instead of caching the pointer value.
+  WallpaperView* wallpaper_view() const { return wallpaper_view_; }
+
   // Reset, and closes both |active_widget_| and |animating_widget_|. Can be
   // used in tests to reset the wallpaper widget controller state.
   void ResetWidgetsForTesting();
@@ -97,6 +112,10 @@
   // shown.
   std::unique_ptr<WidgetHandler> animating_widget_;
 
+  // Pointer to the wallpaper view owned by |animating_widget_| if it exists,
+  // otherwise owned by |active_widget_|.
+  WallpaperView* wallpaper_view_ = nullptr;
+
   // Callbacks to be run when the |animating_widget_| stops animating and gets
   // set as the active widget.
   std::list<base::OnceClosure> animation_end_callbacks_;
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 6bbe587b6..0e2dc9c 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -20,6 +20,7 @@
 #include "ash/wm/overview/overview_item.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/overview/scoped_overview_animation_settings.h"
 #include "ash/wm/root_window_finder.h"
 #include "ash/wm/screen_pinning_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
@@ -33,6 +34,7 @@
 #include "base/metrics/user_metrics.h"
 #include "base/stl_util.h"
 #include "ui/aura/client/aura_constants.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/gfx/animation/animation_delegate.h"
 #include "ui/gfx/animation/slide_animation.h"
 #include "ui/wm/core/window_util.h"
@@ -84,6 +86,30 @@
   return true;
 }
 
+// Helper to dim the wallpaper. Optionally animates the dimming.
+void SetWallpaperDimmed(bool dimmed) {
+  // Wallpaper is already dimmed in tablet mode for the home launcher, so no
+  // need to dim anymore.
+  if (!Shell::Get()->tablet_mode_controller() ||
+      Shell::Get()
+          ->tablet_mode_controller()
+          ->IsTabletModeWindowManagerEnabled()) {
+    return;
+  }
+
+  const float target_opacity = dimmed ? kShieldOpacity : 1.0f;
+  for (aura::Window* root : Shell::Get()->GetAllRootWindows()) {
+    WallpaperWidgetController* wallpaper_widget_controller =
+        RootWindowController::ForWindow(root)->wallpaper_widget_controller();
+    ui::Layer* layer = wallpaper_widget_controller->GetLayer();
+    if (layer) {
+      ScopedOverviewAnimationSettings settings(OVERVIEW_ANIMATION_SHIELD_FADE,
+                                               layer->GetAnimator());
+      layer->SetOpacity(target_opacity);
+    }
+  }
+}
+
 }  // namespace
 
 // Class that handles of blurring wallpaper upon entering and exiting overview
@@ -374,6 +400,7 @@
 void OverviewController::OnStartingAnimationComplete(bool canceled) {
   if (IsBlurAllowed())
     overview_blur_controller_->Blur(/*animate_only=*/true);
+  SetWallpaperDimmed(true);
 
   for (auto& observer : observers_)
     observer.OnOverviewModeStartingAnimationComplete(canceled);
@@ -388,6 +415,7 @@
   // the blur.
   if (IsBlurAllowed() && !canceled)
     overview_blur_controller_->Unblur();
+  SetWallpaperDimmed(false);
 
   for (auto& observer : observers_)
     observer.OnOverviewModeEndingAnimationComplete(canceled);
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 3ae4095..ba84a94 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -11,7 +11,6 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/public/cpp/wallpaper_types.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/root_window_controller.h"
@@ -21,8 +20,6 @@
 #include "ash/shelf/shelf_constants.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/wallpaper/wallpaper_controller.h"
-#include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/drop_target_view.h"
@@ -50,8 +47,6 @@
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/compositor_extra/shadow.h"
-#include "ui/gfx/color_analysis.h"
-#include "ui/gfx/color_utils.h"
 #include "ui/gfx/geometry/safe_integer_conversions.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/views/background.h"
@@ -65,9 +60,6 @@
 namespace ash {
 namespace {
 
-// The color and opacity of the screen shield in overview.
-constexpr SkColor kShieldColor = SkColorSetARGB(255, 0, 0, 0);
-
 // The color and opacity of the overview selector.
 constexpr SkColor kWindowSelectionColor = SkColorSetARGB(36, 255, 255, 255);
 
@@ -75,10 +67,6 @@
 constexpr int kWindowSelectionRadius = 9;
 constexpr int kWindowSelectionShadowElevation = 24;
 
-// The base color which is mixed with the dark muted color from wallpaper to
-// form the shield widgets color.
-constexpr SkColor kShieldBaseColor = SkColorSetARGB(179, 0, 0, 0);
-
 // Windows are not allowed to get taller than this.
 constexpr int kMaxHeight = 512;
 
@@ -195,16 +183,6 @@
 class OverviewGrid::ShieldView : public views::View {
  public:
   ShieldView() {
-    background_view_ = new views::View();
-    background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
-    background_view_->layer()->SetColor(kShieldBaseColor);
-    background_view_->layer()->SetOpacity(
-        !Shell::Get()
-                ->tablet_mode_controller()
-                ->IsTabletModeWindowManagerEnabled()
-            ? kShieldOpacity
-            : 0.f);
-
     label_ = new views::Label(
         l10n_util::GetStringUTF16(IDS_ASH_OVERVIEW_NO_RECENT_ITEMS),
         views::style::CONTEXT_LABEL);
@@ -226,7 +204,6 @@
     label_container_->layer()->SetOpacity(kNoItemsIndicatorBackgroundOpacity);
     label_container_->SetVisible(false);
 
-    AddChildView(background_view_);
     AddChildView(label_container_);
 
     if (features::IsVirtualDesksEnabled()) {
@@ -237,10 +214,6 @@
 
   ~ShieldView() override = default;
 
-  void SetBackgroundColor(SkColor color) {
-    background_view_->layer()->SetColor(color);
-  }
-
   void SetLabelVisibility(bool visible) {
     label_container_->SetVisible(visible);
   }
@@ -268,7 +241,6 @@
  protected:
   // views::View:
   void Layout() override {
-    background_view_->SetBoundsRect(GetLocalBounds());
     UpdateDesksBarBounds();
   }
 
@@ -319,7 +291,6 @@
   }
 
   // Owned by views heirarchy.
-  views::View* background_view_ = nullptr;
   RoundedRectView* label_container_ = nullptr;
   views::Label* label_ = nullptr;
   DesksBarView* desks_bar_view_ = nullptr;
@@ -425,22 +396,6 @@
 
 OverviewGrid::~OverviewGrid() = default;
 
-// static
-SkColor OverviewGrid::GetShieldColor() {
-  SkColor shield_color = kShieldColor;
-  // Extract the dark muted color from the wallpaper and mix it with
-  // |kShieldBaseColor|. Just use |kShieldBaseColor| if the dark muted color
-  // could not be extracted.
-  SkColor dark_muted_color =
-      Shell::Get()->wallpaper_controller()->GetProminentColor(
-          color_utils::ColorProfile());
-  if (dark_muted_color != ash::kInvalidWallpaperColor) {
-    shield_color =
-        color_utils::GetResultingPaintColor(kShieldBaseColor, dark_muted_color);
-  }
-  return shield_color;
-}
-
 void OverviewGrid::Shutdown() {
   ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this);
 
@@ -919,6 +874,16 @@
   PositionWindows(false);
 }
 
+void OverviewGrid::OnWindowPropertyChanged(aura::Window* window,
+                                           const void* key,
+                                           intptr_t old) {
+  if (prepared_for_overview_ && key == aura::client::kTopViewInset &&
+      window->GetProperty(aura::client::kTopViewInset) !=
+          static_cast<int>(old)) {
+    PositionWindows(/*animate=*/false);
+  }
+}
+
 void OverviewGrid::OnPostWindowStateTypeChange(
     wm::WindowState* window_state,
     mojom::WindowStateType old_type) {
@@ -1287,7 +1252,6 @@
   // Create |shield_view_| and animate its background and label if needed.
   shield_view_ = new ShieldView();
   shield_widget_->SetContentsView(shield_view_);
-  shield_view_->SetBackgroundColor(GetShieldColor());
   shield_view_->SetGridBounds(bounds_);
 
   if (animate) {
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index fb7c59c..0ef0f32f 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -59,9 +59,6 @@
                const gfx::Rect& bounds_in_screen);
   ~OverviewGrid() override;
 
-  // Returns the shield color that is used to darken the background of the grid.
-  static SkColor GetShieldColor();
-
   // Exits overview mode, fading out the |shield_widget_| if necessary.
   void Shutdown();
 
@@ -139,11 +136,15 @@
 
   // aura::WindowObserver:
   void OnWindowDestroying(aura::Window* window) override;
-  // TODO(flackr): Handle window bounds changed in OverviewItem.
+  // TODO(flackr): Handle window bounds changed in OverviewItem. See also
+  // OnWindowPropertyChanged() below.
   void OnWindowBoundsChanged(aura::Window* window,
                              const gfx::Rect& old_bounds,
                              const gfx::Rect& new_bounds,
                              ui::PropertyChangeReason reason) override;
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override;
 
   // wm::WindowStateObserver:
   void OnPostWindowStateTypeChange(wm::WindowState* window_state,
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index c21f4696..a2668e3 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -874,6 +874,19 @@
   ToggleOverview();
 }
 
+// Tests that a change to the |kTopViewInset| window property during overview is
+// corrected for.
+TEST_F(OverviewSessionTest, TopViewInsetChangeDuringOverview) {
+  std::unique_ptr<aura::Window> window = CreateTestWindow(gfx::Rect(400, 400));
+  window->SetProperty(aura::client::kTopViewInset, 32);
+  ToggleOverview();
+  gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get());
+  window->SetProperty(aura::client::kTopViewInset, 0);
+  gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get());
+  EXPECT_NE(overview_bounds, new_overview_bounds);
+  ToggleOverview();
+}
+
 // Tests that a newly created window aborts overview.
 TEST_F(OverviewSessionTest, NewWindowCancelsOverview) {
   std::unique_ptr<aura::Window> window1(CreateTestWindow());
diff --git a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
index b51b1f9b..4fc94dc 100644
--- a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
@@ -7,14 +7,15 @@
 #include <vector>
 
 #include "ash/app_list/app_list_controller_impl.h"
+#include "ash/public/cpp/wallpaper_types.h"
 #include "ash/root_window_controller.h"
 #include "ash/scoped_animation_disabler.h"
 #include "ash/shell.h"
+#include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_controller.h"
-#include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
@@ -23,6 +24,8 @@
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/color_analysis.h"
+#include "ui/gfx/color_utils.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
@@ -40,6 +43,29 @@
 // window after drag ends and do not try to merge it back into source window.
 constexpr float kFlingToStayAsNewWindowThreshold = 2000.f;
 
+// The color and opacity of the screen shield.
+constexpr SkColor kShieldColor = SkColorSetARGB(255, 0, 0, 0);
+
+// The base color which is mixed with the dark muted color from wallpaper to
+// form the shield widgets color.
+constexpr SkColor kShieldBaseColor = SkColorSetARGB(179, 0, 0, 0);
+
+// Returns the shield color that is used to darken the background.
+SkColor GetShieldColor() {
+  SkColor shield_color = kShieldColor;
+  // Extract the dark muted color from the wallpaper and mix it with
+  // |kShieldBaseColor|. Just use |kShieldBaseColor| if the dark muted color
+  // could not be extracted.
+  SkColor dark_muted_color =
+      Shell::Get()->wallpaper_controller()->GetProminentColor(
+          color_utils::ColorProfile());
+  if (dark_muted_color != ash::kInvalidWallpaperColor) {
+    shield_color =
+        color_utils::GetResultingPaintColor(kShieldBaseColor, dark_muted_color);
+  }
+  return shield_color;
+}
+
 // The class to observe the source window's bounds change animation. It's used
 // to prevent the dragged window to merge back into the source window during
 // dragging. Only when the source window restores to its maximized window size,
@@ -155,6 +181,8 @@
         ->SetWallpaperBlur(kWallpaperBlurSigma);
 
     // Darken the background.
+    // TODO: Do dimming in wallpaper to avoid creating another fullscreen
+    // widget.
     shield_widget_ = CreateBackgroundWidget(
         root_window, ui::LAYER_SOLID_COLOR, SK_ColorTRANSPARENT, 0, 0,
         SK_ColorTRANSPARENT, /*initial_opacity*/ 1.f, /*parent=*/nullptr,
@@ -164,7 +192,7 @@
     widget_window->SetBounds(bounds);
     views::View* shield_view = new views::View();
     shield_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
-    shield_view->layer()->SetColor(OverviewGrid::GetShieldColor());
+    shield_view->layer()->SetColor(GetShieldColor());
     shield_view->layer()->SetOpacity(kShieldOpacity);
     shield_widget_->SetContentsView(shield_view);
   }
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index dad1f7bc..ce569f3 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 
+#include <algorithm>
+#include <string>
 #include <utility>
 
 #include "ash/public/cpp/ash_switches.h"
@@ -600,6 +602,22 @@
   client_->OnTabletModeToggled(IsTabletModeWindowManagerEnabled());
 }
 
+// Used for testing. Called via Mojo.
+void TabletModeController::SetTabletModeEnabledForTesting(
+    bool enabled,
+    SetTabletModeEnabledForTestingCallback callback) {
+  // Disable Accelerometer and PowerManagerClient observers to prevent possible
+  // tablet mode overrides. It won't be possible to physically switch to/from
+  // tablet mode after calling this function. This is needed for tests that
+  // run on DUTs and require switching to/back tablet mode in runtime, like some
+  // ARC++ Tast tests.
+  AccelerometerReader::GetInstance()->RemoveObserver(this);
+  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(
+      this);
+  EnableTabletModeWindowManager(enabled);
+  std::move(callback).Run(IsTabletModeWindowManagerEnabled());
+}
+
 bool TabletModeController::AllowUiModeChange() const {
   return force_ui_mode_ == UiMode::kNone;
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index c189f5ed..71a56fc 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -203,6 +203,9 @@
 
   // mojom::TabletModeController:
   void SetClient(mojom::TabletModeClientPtr client) override;
+  void SetTabletModeEnabledForTesting(
+      bool enabled,
+      SetTabletModeEnabledForTestingCallback callback) override;
 
   // Checks whether we want to allow change the current ui mode to tablet mode
   // or clamshell mode. This returns false if the user set a flag for the
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 3f1dcdc..2c7a081 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -18,9 +18,11 @@
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
 #include "ash/wm/wm_event.h"
 #include "base/command_line.h"
@@ -32,6 +34,7 @@
 #include "chromeos/dbus/fake_power_manager_client.h"
 #include "services/ws/public/cpp/input_devices/input_device_client_test_api.h"
 #include "ui/aura/client/aura_constants.h"
+#include "ui/aura/test/test_window_delegate.h"
 #include "ui/base/hit_test.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
@@ -1321,6 +1324,136 @@
   EXPECT_FALSE(GetDeferBoundsUpdates(snapped_window.get()));
 }
 
+// Test that if before tablet mode, the active window is snapped on the left but
+// does not meet the requirements to be snapped in split view, and the previous
+// window is snapped on the right, then split view is not activated.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveDesktopOnlyLeftSnapPreviousRightSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  aura::test::TestWindowDelegate left_window_delegate;
+  std::unique_ptr<aura::Window> left_window(CreateTestWindowInShellWithDelegate(
+      &left_window_delegate, /*id=*/-1, /*bounds=*/gfx::Rect(0, 0, 400, 400)));
+  const gfx::Rect display_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInScreenForDefaultContainer(
+          left_window.get());
+  left_window_delegate.set_minimum_size(
+      gfx::Size(display_bounds.width() * 0.67f, display_bounds.height()));
+  wm::WindowState* left_window_state = wm::GetWindowState(left_window.get());
+  ASSERT_TRUE(left_window_state->CanSnap());
+  ASSERT_FALSE(CanSnapInSplitview(left_window.get()));
+  wm::WMEvent snap_to_left(wm::WM_EVENT_CYCLE_SNAP_LEFT);
+  left_window_state->OnWMEvent(&snap_to_left);
+  std::unique_ptr<aura::Window> right_window =
+      CreateDesktopWindowSnappedRight();
+  ::wm::ActivateWindow(right_window.get());
+  ::wm::ActivateWindow(left_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::NO_SNAP, split_view_controller->state());
+  EXPECT_FALSE(Shell::Get()->overview_controller()->IsSelecting());
+  EXPECT_FALSE(GetDeferBoundsUpdates(left_window.get()));
+  EXPECT_FALSE(GetDeferBoundsUpdates(right_window.get()));
+}
+
+// Test that if before tablet mode, the active window is snapped on the right
+// but does not meet the requirements to be snapped in split view, and the
+// previous window is snapped on the left, then split view is not activated.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveDesktopOnlyRightSnapPreviousLeftSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
+  aura::test::TestWindowDelegate right_window_delegate;
+  std::unique_ptr<aura::Window> right_window(
+      CreateTestWindowInShellWithDelegate(
+          &right_window_delegate, /*id=*/-1,
+          /*bounds=*/gfx::Rect(0, 0, 400, 400)));
+  const gfx::Rect display_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInScreenForDefaultContainer(
+          right_window.get());
+  right_window_delegate.set_minimum_size(
+      gfx::Size(display_bounds.width() * 0.67f, display_bounds.height()));
+  wm::WindowState* right_window_state = wm::GetWindowState(right_window.get());
+  ASSERT_TRUE(right_window_state->CanSnap());
+  ASSERT_FALSE(CanSnapInSplitview(right_window.get()));
+  wm::WMEvent snap_to_right(wm::WM_EVENT_CYCLE_SNAP_RIGHT);
+  right_window_state->OnWMEvent(&snap_to_right);
+  ::wm::ActivateWindow(left_window.get());
+  ::wm::ActivateWindow(right_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::NO_SNAP, split_view_controller->state());
+  EXPECT_FALSE(Shell::Get()->overview_controller()->IsSelecting());
+  EXPECT_FALSE(GetDeferBoundsUpdates(left_window.get()));
+  EXPECT_FALSE(GetDeferBoundsUpdates(right_window.get()));
+}
+
+// Test that if before tablet mode, the active window is snapped on the left and
+// the previous window is snapped on the right but does not meet the
+// requirements to be snapped in split view, then split view is activated with
+// the active window on the left.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveLeftSnapPreviousDesktopOnlyRightSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
+  aura::test::TestWindowDelegate right_window_delegate;
+  std::unique_ptr<aura::Window> right_window(
+      CreateTestWindowInShellWithDelegate(
+          &right_window_delegate, /*id=*/-1,
+          /*bounds=*/gfx::Rect(0, 0, 400, 400)));
+  const gfx::Rect display_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInScreenForDefaultContainer(
+          right_window.get());
+  right_window_delegate.set_minimum_size(
+      gfx::Size(display_bounds.width() * 0.67f, display_bounds.height()));
+  wm::WindowState* right_window_state = wm::GetWindowState(right_window.get());
+  ASSERT_TRUE(right_window_state->CanSnap());
+  ASSERT_FALSE(CanSnapInSplitview(right_window.get()));
+  wm::WMEvent snap_to_right(wm::WM_EVENT_CYCLE_SNAP_RIGHT);
+  right_window_state->OnWMEvent(&snap_to_right);
+  ::wm::ActivateWindow(right_window.get());
+  ::wm::ActivateWindow(left_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::LEFT_SNAPPED, split_view_controller->state());
+  EXPECT_EQ(left_window.get(), split_view_controller->left_window());
+  EXPECT_TRUE(Shell::Get()->overview_controller()->IsSelecting());
+  EXPECT_FALSE(GetDeferBoundsUpdates(left_window.get()));
+  EXPECT_TRUE(GetDeferBoundsUpdates(right_window.get()));
+}
+
+// Test that if before tablet mode, the active window is snapped on the right
+// and the previous window is snapped on the left but does not meet the
+// requirements to be snapped in split view, then split view is activated with
+// the active window on the right.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveRightSnapPreviousDesktopOnlyLeftSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  aura::test::TestWindowDelegate left_window_delegate;
+  std::unique_ptr<aura::Window> left_window(CreateTestWindowInShellWithDelegate(
+      &left_window_delegate, /*id=*/-1, /*bounds=*/gfx::Rect(0, 0, 400, 400)));
+  const gfx::Rect display_bounds =
+      screen_util::GetDisplayWorkAreaBoundsInScreenForDefaultContainer(
+          left_window.get());
+  left_window_delegate.set_minimum_size(
+      gfx::Size(display_bounds.width() * 0.67f, display_bounds.height()));
+  wm::WindowState* left_window_state = wm::GetWindowState(left_window.get());
+  ASSERT_TRUE(left_window_state->CanSnap());
+  ASSERT_FALSE(CanSnapInSplitview(left_window.get()));
+  wm::WMEvent snap_to_left(wm::WM_EVENT_CYCLE_SNAP_LEFT);
+  left_window_state->OnWMEvent(&snap_to_left);
+  std::unique_ptr<aura::Window> right_window =
+      CreateDesktopWindowSnappedRight();
+  ::wm::ActivateWindow(left_window.get());
+  ::wm::ActivateWindow(right_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::RIGHT_SNAPPED, split_view_controller->state());
+  EXPECT_EQ(right_window.get(), split_view_controller->right_window());
+  EXPECT_TRUE(Shell::Get()->overview_controller()->IsSelecting());
+  EXPECT_TRUE(GetDeferBoundsUpdates(left_window.get()));
+  EXPECT_FALSE(GetDeferBoundsUpdates(right_window.get()));
+}
+
 // Test that if overview is triggered on entering tablet mode, then the app list
 // can still be successfully shown and actually seen.
 TEST_F(TabletModeControllerTest, AppListWorksAfterEnteringTabletForOverview) {
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index b332f404..4ea4301 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -14,6 +14,7 @@
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_session.h"
+#include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/tablet_mode/scoped_skip_user_session_blocked_check.h"
 #include "ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h"
 #include "ash/wm/tablet_mode/tablet_mode_event_handler.h"
@@ -310,9 +311,10 @@
   MruWindowTracker::WindowList activatable_windows =
       Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal();
 
-  // If split_view_eligible_windows[0] does not exist or is ARC or not snapped,
-  // then just maximize all windows.
+  // If split_view_eligible_windows[0] does not exist, cannot be snapped in
+  // split view, or is ARC or not snapped, then just maximize all windows.
   if (split_view_eligible_windows.empty() ||
+      !CanSnapInSplitview(split_view_eligible_windows[0]) ||
       static_cast<ash::AppType>(split_view_eligible_windows[0]->GetProperty(
           aura::client::kAppType)) == AppType::ARC_APP ||
       !wm::GetWindowState(split_view_eligible_windows[0])->IsSnapped()) {
@@ -323,9 +325,10 @@
 
   // Carry over split_view_eligible_windows[0] to split view, along with
   // split_view_eligible_windows[1] if it exists, is snapped on the opposite
-  // side, and is not ARC.
-  const bool prev_win_not_arc =
+  // side, can be snapped in split view, and is not ARC.
+  const bool prev_win_eligible =
       split_view_eligible_windows.size() > 1u &&
+      CanSnapInSplitview(split_view_eligible_windows[1]) &&
       static_cast<ash::AppType>(split_view_eligible_windows[1]->GetProperty(
           aura::client::kAppType)) != AppType::ARC_APP;
   std::vector<SplitViewController::SnapPosition> snap_positions;
@@ -334,7 +337,7 @@
     // split_view_eligible_windows[0] goes on the left.
     snap_positions.push_back(SplitViewController::LEFT);
 
-    if (prev_win_not_arc &&
+    if (prev_win_eligible &&
         wm::GetWindowState(split_view_eligible_windows[1])->GetStateType() ==
             mojom::WindowStateType::RIGHT_SNAPPED) {
       // split_view_eligible_windows[1] goes on the right.
@@ -348,7 +351,7 @@
     // split_view_eligible_windows[0] goes on the right.
     snap_positions.push_back(SplitViewController::RIGHT);
 
-    if (prev_win_not_arc &&
+    if (prev_win_eligible &&
         wm::GetWindowState(split_view_eligible_windows[1])->GetStateType() ==
             mojom::WindowStateType::LEFT_SNAPPED) {
       // split_view_eligible_windows[1] goes on the left.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index d74d494..1d34045a 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -177,12 +177,9 @@
       animate_bounds_on_attach_(animate_bounds_on_attach) {
   wm::WindowState* state = wm::GetWindowState(window);
   current_state_type_ = state->GetStateType();
-  // The /*target_state=*/current_state_type_ part is because if |snap| is true,
-  // then we are carrying over a snapped state from desktop mode to tablet mode.
-  state_type_on_attach_ = snap
-                              ? GetSnappedWindowStateType(
-                                    state, /*target_state=*/current_state_type_)
-                              : GetMaximizedOrCenteredWindowType(state);
+  DCHECK(!snap || CanSnapInSplitview(window));
+  state_type_on_attach_ =
+      snap ? current_state_type_ : GetMaximizedOrCenteredWindowType(state);
   old_state_.reset(
       state->SetStateObject(std::unique_ptr<State>(this)).release());
 }
diff --git a/base/allocator/BUILD.gn b/base/allocator/BUILD.gn
index 8e72ebd..c5d71935 100644
--- a/base/allocator/BUILD.gn
+++ b/base/allocator/BUILD.gn
@@ -143,9 +143,6 @@
       "$tcmalloc_dir/src/gperftools/malloc_extension.h",
       "$tcmalloc_dir/src/gperftools/malloc_hook.h",
       "$tcmalloc_dir/src/gperftools/stacktrace.h",
-      "$tcmalloc_dir/src/heap-profile-table.cc",
-      "$tcmalloc_dir/src/heap-profile-table.h",
-      "$tcmalloc_dir/src/heap-profiler.cc",
       "$tcmalloc_dir/src/internal_logging.cc",
       "$tcmalloc_dir/src/internal_logging.h",
       "$tcmalloc_dir/src/linked_list.h",
@@ -154,8 +151,6 @@
       "$tcmalloc_dir/src/malloc_hook.cc",
       "$tcmalloc_dir/src/maybe_threads.cc",
       "$tcmalloc_dir/src/maybe_threads.h",
-      "$tcmalloc_dir/src/memory_region_map.cc",
-      "$tcmalloc_dir/src/memory_region_map.h",
       "$tcmalloc_dir/src/page_heap.cc",
       "$tcmalloc_dir/src/page_heap.h",
       "$tcmalloc_dir/src/raw_printer.cc",
@@ -238,6 +233,11 @@
       sources += [
         "$tcmalloc_dir/src/base/thread_lister.c",
         "$tcmalloc_dir/src/base/thread_lister.h",
+        "$tcmalloc_dir/src/heap-profile-table.cc",
+        "$tcmalloc_dir/src/heap-profile-table.h",
+        "$tcmalloc_dir/src/heap-profiler.cc",
+        "$tcmalloc_dir/src/memory_region_map.cc",
+        "$tcmalloc_dir/src/memory_region_map.h",
         "$tcmalloc_dir/src/profile-handler.cc",
         "$tcmalloc_dir/src/profile-handler.h",
         "$tcmalloc_dir/src/profiledata.cc",
diff --git a/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc
index fc1fc506..232c02a 100644
--- a/base/allocator/allocator_extension.cc
+++ b/base/allocator/allocator_extension.cc
@@ -48,7 +48,7 @@
 }
 
 bool IsHeapProfilerRunning() {
-#if defined(USE_TCMALLOC)
+#if defined(USE_TCMALLOC) && defined(ENABLE_PROFILING)
   return ::IsHeapProfilerRunning();
 #endif
   return false;
diff --git a/base/android/jni_generator/golden/testProxyNativesWithNatives.golden b/base/android/jni_generator/golden/testProxyNativesWithNatives.golden
index 8486282..e0b3576 100644
--- a/base/android/jni_generator/golden/testProxyNativesWithNatives.golden
+++ b/base/android/jni_generator/golden/testProxyNativesWithNatives.golden
@@ -92,5 +92,26 @@
   return JNI_Foo_Foobar(env, base::android::JavaParamRef<jobjectArray>(env, a)).Release();
 }
 
+JNI_GENERATOR_EXPORT void Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1foo_1Foo_1baz(
+    JNIEnv* env,
+    jobject jcaller,
+    jobject caller,
+    jlong nativePtr) {
+  TRACE_EVENT0("jni", "Ptr::Baz");
+  Ptr* native = reinterpret_cast<Ptr*>(nativePtr);
+  CHECK_NATIVE_PTR(env, jcaller, native, "Baz");
+  return native->Baz(env, base::android::JavaParamRef<jobject>(env, caller));
+}
+
+JNI_GENERATOR_EXPORT void Java_org_chromium_base_natives_GEN_1JNI_org_1chromium_1foo_1Foo_1fooBar(
+    JNIEnv* env,
+    jobject jcaller,
+    jlong nativePtr) {
+  TRACE_EVENT0("jni", "Ptr::FooBar");
+  Ptr* native = reinterpret_cast<Ptr*>(nativePtr);
+  CHECK_NATIVE_PTR(env, jcaller, native, "FooBar");
+  return native->FooBar(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
 
 #endif  // org_chromium_foo_Foo_JNI
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 75ec34f5..d36cdfb 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -121,9 +121,16 @@
     self.static = kwargs['static']
     self.java_class_name = kwargs['java_class_name']
     self.return_type = kwargs['return_type']
-    self.name = kwargs['name']
     self.params = kwargs['params']
     self.is_proxy = kwargs.get('is_proxy', False)
+
+    self.name = kwargs['name']
+    if self.is_proxy:
+      # Proxy methods don't have a native prefix so the first letter is
+      # lowercase. But we still want the CPP declaration to use upper camel
+      # case for the method name.
+      self.name = self.name[0].upper() + self.name[1:]
+
     self.proxy_name = kwargs.get('proxy_name', self.name)
 
     has_jcaller = False
@@ -1207,14 +1214,7 @@
       # Inner class
       class_name = native.java_class_name
 
-    method_name = native.name
-    if native.is_proxy:
-      # proxy methods don't have a native prefix so the first letter is
-      # lowercase. But we still want the CPP declaration to use upper camel case
-      # for the method name.
-      method_name = method_name[0].upper() + method_name[1:]
-
-    return 'JNI_%s_%s' % (class_name, method_name)
+    return 'JNI_%s_%s' % (class_name, native.name)
 
   def GetNativeStub(self, native):
     is_method = native.type == 'method'
diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py
index 4b927dc..f6d305e4 100755
--- a/base/android/jni_generator/jni_generator_tests.py
+++ b/base/android/jni_generator/jni_generator_tests.py
@@ -29,6 +29,8 @@
 INCLUDES = (
     'base/android/jni_generator/jni_generator_helper.h'
 )
+_JAVA_SRC_DIR = os.path.join('java', 'src', 'org', 'chromium', 'example',
+                             'jni_generator')
 
 # Set this environment variable in order to regenerate the golden text
 # files.
@@ -146,8 +148,8 @@
     """Compares generated text with the corresponding golden_file
 
     By default compares generated_text with the file at
-    script_dir/golden/{caller_name}[suffix].golden. If the parameter golden_file is
-    provided it will instead compare the generated text with
+    script_dir/golden/{caller_name}[suffix].golden. If the parameter
+    golden_file is provided it will instead compare the generated text with
     script_dir/golden/golden_file."""
     # This is the caller test method.
     caller = inspect.stack()[1][3]
@@ -955,7 +957,7 @@
 
   def testJniSelfDocumentingExample(self):
     generated_text = self._CreateJniHeaderFromFile(
-        'java/src/org/chromium/example/jni_generator/SampleForTests.java',
+        os.path.join(_JAVA_SRC_DIR, 'SampleForTests.java'),
         'org/chromium/example/jni_generator/SampleForTests')
     self.AssertGoldenTextEquals(
         generated_text, golden_file='SampleForTests_jni.golden')
@@ -1253,8 +1255,7 @@
       options = TestOptions()
 
     path = self._JoinScriptDir(
-        'java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java'
-    )
+        os.path.join(_JAVA_SRC_DIR, 'SampleForAnnotationProcessor.java'))
     reg_dict = jni_registration_generator._DictForPath(path)
     reg_dict = self._MergeRegistrationForTests([reg_dict])
 
@@ -1271,6 +1272,8 @@
        void foo();
        String bar(String s, int y, char x, short z);
        String[] foobar(String[] a);
+       void baz(@JCaller BazClass caller, long nativePtr);
+       void fooBar(long nativePtr);
     }
 
     void justARegularFunction();
@@ -1495,7 +1498,7 @@
   def testProxyHashedExample(self):
     opts = TestOptions()
     opts.use_proxy_hash = True
-    path = 'java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java'
+    path = os.path.join(_JAVA_SRC_DIR, 'SampleForAnnotationProcessor.java')
 
     generated_text = self._CreateJniHeaderFromFile(
         path, 'org/chromium/example/jni_generator/SampleForAnnotationProcessor',
@@ -1516,7 +1519,7 @@
 
   def testProxyJniExample(self):
     generated_text = self._CreateJniHeaderFromFile(
-        'java/src/org/chromium/example/jni_generator/SampleForAnnotationProcessor.java',
+        os.path.join(_JAVA_SRC_DIR, 'SampleForAnnotationProcessor.java'),
         'org/chromium/example/jni_generator/SampleForAnnotationProcessor')
     self.AssertGoldenTextEquals(
         generated_text, golden_file='SampleForAnnotationProcessor_jni.golden')
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index f07497e..d1dcf4e 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -9,9 +9,6 @@
 
 #if defined(COMPILER_MSVC)
 
-// For _Printf_format_string_.
-#include <sal.h>
-
 // Macros for suppressing and disabling warnings on MSVC.
 //
 // Warning numbers are enumerated at:
@@ -33,7 +30,6 @@
 
 #else  // Not MSVC
 
-#define _Printf_format_string_
 #define MSVC_PUSH_DISABLE_WARNING(n)
 #define MSVC_POP_WARNING()
 #define MSVC_DISABLE_OPTIMIZE()
@@ -138,6 +134,7 @@
 // |dots_param| is the one-based index of the "..." parameter.
 // For v*printf functions (which take a va_list), pass 0 for dots_param.
 // (This is undocumented but matches what the system C headers do.)
+// For member functions, the implicit this parameter counts as index 1.
 #if defined(COMPILER_GCC) || defined(__clang__)
 #define PRINTF_FORMAT(format_param, dots_param) \
     __attribute__((format(printf, format_param, dots_param)))
diff --git a/base/mac/sdk_forward_declarations.h b/base/mac/sdk_forward_declarations.h
index 6a4cd64..4ad015c 100644
--- a/base/mac/sdk_forward_declarations.h
+++ b/base/mac/sdk_forward_declarations.h
@@ -16,6 +16,7 @@
 #import <CoreWLAN/CoreWLAN.h>
 #import <IOBluetooth/IOBluetooth.h>
 #import <ImageCaptureCore/ImageCaptureCore.h>
+#import <LocalAuthentication/LocalAuthentication.h>
 #import <QuartzCore/QuartzCore.h>
 #include <stdint.h>
 
@@ -69,6 +70,22 @@
 
 #endif  // MAC_OS_X_VERSION_10_12
 
+#if !defined(MAC_OS_X_VERSION_10_13_2) || \
+    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13_2
+
+enum {
+  LABiometryTypeNone = 0,
+  LABiometryTypeFaceID = 1,
+  LABiometryTypeTouchID = 2
+};
+typedef NSInteger LABiometryType;
+
+@interface LAContext (HighSierraPointTwoSDK)
+@property(nonatomic, readonly) LABiometryType biometryType;
+@end
+
+#endif  // MAC_OS_X_VERSION_10_13_2
+
 // ----------------------------------------------------------------------------
 // Define NSStrings only available in newer versions of the OSX SDK to force
 // them to be statically linked.
diff --git a/base/profiler/stack_sampling_profiler_unittest.cc b/base/profiler/stack_sampling_profiler_unittest.cc
index b11350f..90d73f54 100644
--- a/base/profiler/stack_sampling_profiler_unittest.cc
+++ b/base/profiler/stack_sampling_profiler_unittest.cc
@@ -562,7 +562,7 @@
   for (const auto& frame : frames) {
     output += StringPrintf(
         "0x%p %s\n", reinterpret_cast<const void*>(frame.instruction_pointer),
-        frame.module->GetFilename().AsUTF8Unsafe().c_str());
+        frame.module->GetDebugBasename().AsUTF8Unsafe().c_str());
   }
   return output;
 }
diff --git a/base/sampling_heap_profiler/module_cache.cc b/base/sampling_heap_profiler/module_cache.cc
index 9de7bac..2fb9369 100644
--- a/base/sampling_heap_profiler/module_cache.cc
+++ b/base/sampling_heap_profiler/module_cache.cc
@@ -10,14 +10,12 @@
 
 ModuleCache::Module::Module(uintptr_t base_address,
                             const std::string& id,
-                            const FilePath& filename)
-    : Module(base_address, id, filename, 0) {}
-
-ModuleCache::Module::Module(uintptr_t base_address,
-                            const std::string& id,
-                            const FilePath& filename,
+                            const FilePath& debug_basename,
                             size_t size)
-    : base_address_(base_address), id_(id), filename_(filename), size_(size) {}
+    : base_address_(base_address),
+      id_(id),
+      debug_basename_(debug_basename),
+      size_(size) {}
 
 ModuleCache::Module::~Module() = default;
 
@@ -29,8 +27,8 @@
   return id_;
 }
 
-FilePath ModuleCache::Module::GetFilename() const {
-  return filename_;
+FilePath ModuleCache::Module::GetDebugBasename() const {
+  return debug_basename_;
 }
 
 size_t ModuleCache::Module::GetSize() const {
diff --git a/base/sampling_heap_profiler/module_cache.h b/base/sampling_heap_profiler/module_cache.h
index 39b62f17..8755b8e5 100644
--- a/base/sampling_heap_profiler/module_cache.h
+++ b/base/sampling_heap_profiler/module_cache.h
@@ -28,9 +28,6 @@
    public:
     Module(uintptr_t base_address,
            const std::string& id,
-           const FilePath& filename);
-    Module(uintptr_t base_address,
-           const std::string& id,
            const FilePath& filename,
            size_t size);
     ~Module();
@@ -50,11 +47,9 @@
     //   GUID + AGE in the debug image headers of a module.
     std::string GetId() const;
 
-    // Gets the filename of the module.
-    // TODO(wittman): This is really the debug basename of the file, which is
-    // the pdb basename for Windows and the binary basename for other
-    // platforms. Update the method name accordingly.
-    FilePath GetFilename() const;
+    // Gets the debug basename of the module. This is the basename of the PDB
+    // file on Windows and the basename of the binary on other platforms.
+    FilePath GetDebugBasename() const;
 
     // Gets the size of the module.
     size_t GetSize() const;
@@ -62,7 +57,7 @@
    private:
     uintptr_t base_address_;
     std::string id_;
-    FilePath filename_;
+    FilePath debug_basename_;
     size_t size_;
   };
 
diff --git a/base/sampling_heap_profiler/module_cache_mac.cc b/base/sampling_heap_profiler/module_cache_mac.cc
index 7571098f..287a860 100644
--- a/base/sampling_heap_profiler/module_cache_mac.cc
+++ b/base/sampling_heap_profiler/module_cache_mac.cc
@@ -71,8 +71,8 @@
     return nullptr;
   auto base_module_address = reinterpret_cast<uintptr_t>(inf.dli_fbase);
   return std::make_unique<Module>(
-      base_module_address, GetUniqueId(inf.dli_fbase), FilePath(inf.dli_fname),
-      GetModuleTextSize(inf.dli_fbase));
+      base_module_address, GetUniqueId(inf.dli_fbase),
+      FilePath(inf.dli_fname).BaseName(), GetModuleTextSize(inf.dli_fbase));
 }
 
 size_t ModuleCache::GetModuleTextSize(const void* module_addr) {
diff --git a/base/strings/string_util.h b/base/strings/string_util.h
index 002e513d..4de71656d 100644
--- a/base/strings/string_util.h
+++ b/base/strings/string_util.h
@@ -41,14 +41,9 @@
 
 // We separate the declaration from the implementation of this inline
 // function just so the PRINTF_FORMAT works.
-inline int snprintf(char* buffer,
-                    size_t size,
-                    _Printf_format_string_ const char* format,
-                    ...) PRINTF_FORMAT(3, 4);
-inline int snprintf(char* buffer,
-                    size_t size,
-                    _Printf_format_string_ const char* format,
-                    ...) {
+inline int snprintf(char* buffer, size_t size, const char* format, ...)
+    PRINTF_FORMAT(3, 4);
+inline int snprintf(char* buffer, size_t size, const char* format, ...) {
   va_list arguments;
   va_start(arguments, format);
   int result = vsnprintf(buffer, size, format, arguments);
diff --git a/base/strings/stringprintf.h b/base/strings/stringprintf.h
index 7a75d89e..341c2ef 100644
--- a/base/strings/stringprintf.h
+++ b/base/strings/stringprintf.h
@@ -16,13 +16,11 @@
 namespace base {
 
 // Return a C++ string given printf-like input.
-BASE_EXPORT std::string StringPrintf(_Printf_format_string_ const char* format,
-                                     ...)
+BASE_EXPORT std::string StringPrintf(const char* format, ...)
     PRINTF_FORMAT(1, 2) WARN_UNUSED_RESULT;
 #if defined(OS_WIN)
-BASE_EXPORT std::wstring StringPrintf(
-    _Printf_format_string_ const wchar_t* format,
-    ...) WPRINTF_FORMAT(1, 2) WARN_UNUSED_RESULT;
+BASE_EXPORT std::wstring StringPrintf(const wchar_t* format, ...)
+    WPRINTF_FORMAT(1, 2) WARN_UNUSED_RESULT;
 #endif
 
 // Return a C++ string given vprintf-like input.
@@ -30,25 +28,21 @@
     PRINTF_FORMAT(1, 0) WARN_UNUSED_RESULT;
 
 // Store result into a supplied string and return it.
-BASE_EXPORT const std::string& SStringPrintf(
-    std::string* dst,
-    _Printf_format_string_ const char* format,
-    ...) PRINTF_FORMAT(2, 3);
+BASE_EXPORT const std::string& SStringPrintf(std::string* dst,
+                                             const char* format,
+                                             ...) PRINTF_FORMAT(2, 3);
 #if defined(OS_WIN)
-BASE_EXPORT const std::wstring& SStringPrintf(
-    std::wstring* dst,
-    _Printf_format_string_ const wchar_t* format,
-    ...) WPRINTF_FORMAT(2, 3);
+BASE_EXPORT const std::wstring& SStringPrintf(std::wstring* dst,
+                                              const wchar_t* format,
+                                              ...) WPRINTF_FORMAT(2, 3);
 #endif
 
 // Append result to a supplied string.
-BASE_EXPORT void StringAppendF(std::string* dst,
-                               _Printf_format_string_ const char* format,
-                               ...) PRINTF_FORMAT(2, 3);
+BASE_EXPORT void StringAppendF(std::string* dst, const char* format, ...)
+    PRINTF_FORMAT(2, 3);
 #if defined(OS_WIN)
-BASE_EXPORT void StringAppendF(std::wstring* dst,
-                               _Printf_format_string_ const wchar_t* format,
-                               ...) WPRINTF_FORMAT(2, 3);
+BASE_EXPORT void StringAppendF(std::wstring* dst, const wchar_t* format, ...)
+    WPRINTF_FORMAT(2, 3);
 #endif
 
 // Lower-level routine that takes a va_list and appends to a specified
@@ -57,8 +51,8 @@
     PRINTF_FORMAT(2, 0);
 #if defined(OS_WIN)
 BASE_EXPORT void StringAppendV(std::wstring* dst,
-                               const wchar_t* format, va_list ap)
-    WPRINTF_FORMAT(2, 0);
+                               const wchar_t* format,
+                               va_list ap) WPRINTF_FORMAT(2, 0);
 #endif
 
 }  // namespace base
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 59f6585..5ccf237d 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -228,6 +228,9 @@
 
     # Enables instantiation of 64-bit browser targets.
     enable_64_bit_browser = true
+
+    # Enable the chrome build for devices without touchscreens.
+    notouch_build = false
   }
 
   assert(!(check_android_configuration && is_java_debug),
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 2131538..de375fb 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8b15beb626f57b6d7e1ede069a66c3b12ba5082c
\ No newline at end of file
+e1ac07a9548de112bac55dd44aa9a1c2be739eb7
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 59fec0c3..42a2469b 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-3a9a51413df3ba4a69a78c0e0b8a8cf65a3b41cd
\ No newline at end of file
+60d620682875fff5249e185519cc8d09c857279a
\ No newline at end of file
diff --git a/build/sanitizers/tsan_suppressions.cc b/build/sanitizers/tsan_suppressions.cc
index 32a3712..c00d288 100644
--- a/build/sanitizers/tsan_suppressions.cc
+++ b/build/sanitizers/tsan_suppressions.cc
@@ -254,9 +254,6 @@
     // http://crbug.com/797998
     "race:content::SandboxIPCHandler::HandleLocaltime\n"
 
-    // http://crbug.com/910524
-    "race:base::subtle::ScopedTimeClockOverrides::ScopedTimeClockOverrides\n"
-
     // End of suppressions.
     ;  // Please keep this semicolon.
 
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index 46fab01..4c01b24 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -271,6 +271,11 @@
   supports_scroll_animations_ = supports_scroll_animations;
 }
 
+void AnimationHost::SetScrollAnimationDurationForTesting(
+    base::TimeDelta duration) {
+  ScrollOffsetAnimationCurve::SetAnimationDurationForTesting(duration);
+}
+
 bool AnimationHost::SupportsScrollAnimations() const {
   return supports_scroll_animations_;
 }
diff --git a/cc/animation/animation_host.h b/cc/animation/animation_host.h
index e3fc873..7d5a511 100644
--- a/cc/animation/animation_host.h
+++ b/cc/animation/animation_host.h
@@ -98,6 +98,7 @@
   void PushPropertiesTo(MutatorHost* host_impl) override;
 
   void SetSupportsScrollAnimations(bool supports_scroll_animations) override;
+  void SetScrollAnimationDurationForTesting(base::TimeDelta duration) override;
   bool NeedsTickAnimations() const override;
 
   bool ActivateAnimations() override;
diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc
index fe61dc63..4dedca4 100644
--- a/cc/animation/scroll_offset_animation_curve.cc
+++ b/cc/animation/scroll_offset_animation_curve.cc
@@ -57,6 +57,9 @@
 
 }  // namespace
 
+base::Optional<double>
+    ScrollOffsetAnimationCurve::animation_duration_for_testing_;
+
 std::unique_ptr<ScrollOffsetAnimationCurve> ScrollOffsetAnimationCurve::Create(
     const gfx::ScrollOffset& target_value,
     std::unique_ptr<TimingFunction> timing_function,
@@ -81,23 +84,28 @@
     DurationBehavior behavior,
     base::TimeDelta delayed_by) {
   double duration = kConstantDuration;
-  switch (behavior) {
-    case DurationBehavior::CONSTANT:
-      duration = kConstantDuration;
-      break;
-    case DurationBehavior::DELTA_BASED:
-      duration = std::min(double(std::sqrt(std::abs(MaximumDimension(delta)))),
-                          kDeltaBasedMaxDuration);
-      break;
-    case DurationBehavior::INVERSE_DELTA:
-      duration = std::min(
-          std::max(kInverseDeltaOffset +
-                       std::abs(MaximumDimension(delta)) * kInverseDeltaSlope,
-                   kInverseDeltaMinDuration),
-          kInverseDeltaMaxDuration);
-      break;
-    default:
-      NOTREACHED();
+  if (!animation_duration_for_testing_) {
+    switch (behavior) {
+      case DurationBehavior::CONSTANT:
+        duration = kConstantDuration;
+        break;
+      case DurationBehavior::DELTA_BASED:
+        duration =
+            std::min(double(std::sqrt(std::abs(MaximumDimension(delta)))),
+                     kDeltaBasedMaxDuration);
+        break;
+      case DurationBehavior::INVERSE_DELTA:
+        duration = std::min(
+            std::max(kInverseDeltaOffset +
+                         std::abs(MaximumDimension(delta)) * kInverseDeltaSlope,
+                     kInverseDeltaMinDuration),
+            kInverseDeltaMaxDuration);
+        break;
+      default:
+        NOTREACHED();
+    }
+  } else {
+    duration = animation_duration_for_testing_.value();
   }
 
   base::TimeDelta time_delta = base::TimeDelta::FromMicroseconds(
@@ -175,6 +183,11 @@
   return curve_clone;
 }
 
+void ScrollOffsetAnimationCurve::SetAnimationDurationForTesting(
+    base::TimeDelta duration) {
+  animation_duration_for_testing_ = duration.InSecondsF() * kDurationDivisor;
+}
+
 static base::TimeDelta VelocityBasedDurationBound(
     gfx::Vector2dF old_delta,
     double old_normalized_velocity,
diff --git a/cc/animation/scroll_offset_animation_curve.h b/cc/animation/scroll_offset_animation_curve.h
index 6924c1e..978fe82 100644
--- a/cc/animation/scroll_offset_animation_curve.h
+++ b/cc/animation/scroll_offset_animation_curve.h
@@ -73,6 +73,8 @@
   std::unique_ptr<ScrollOffsetAnimationCurve>
   CloneToScrollOffsetAnimationCurve() const;
 
+  static void SetAnimationDurationForTesting(base::TimeDelta duration);
+
  private:
   ScrollOffsetAnimationCurve(const gfx::ScrollOffset& target_value,
                              std::unique_ptr<TimingFunction> timing_function,
@@ -90,6 +92,8 @@
 
   bool has_set_initial_value_;
 
+  static base::Optional<double> animation_duration_for_testing_;
+
   DISALLOW_COPY_AND_ASSIGN(ScrollOffsetAnimationCurve);
 };
 
diff --git a/cc/base/switches.cc b/cc/base/switches.cc
index 22db5dd..b4d01b9b 100644
--- a/cc/base/switches.cc
+++ b/cc/base/switches.cc
@@ -92,5 +92,9 @@
 // Makes pixel tests write their output instead of read it.
 const char kCCRebaselinePixeltests[] = "cc-rebaseline-pixeltests";
 
+// Controls the duration of the scroll animation curve.
+const char kCCScrollAnimationDurationForTesting[] =
+    "cc-scroll-animation-duration-in-seconds";
+
 }  // namespace switches
 }  // namespace cc
diff --git a/cc/base/switches.h b/cc/base/switches.h
index cde4700..36d6e41 100644
--- a/cc/base/switches.h
+++ b/cc/base/switches.h
@@ -52,6 +52,7 @@
 CC_BASE_EXPORT extern const char kCCLayerTreeTestNoTimeout[];
 CC_BASE_EXPORT extern const char kCCLayerTreeTestLongTimeout[];
 CC_BASE_EXPORT extern const char kCCRebaselinePixeltests[];
+CC_BASE_EXPORT extern const char kCCScrollAnimationDurationForTesting[];
 
 }  // namespace switches
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 6db0c880..73f2e910 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -499,6 +499,11 @@
   std::unique_ptr<MutatorHost> mutator_host_impl =
       mutator_host_->CreateImplInstance(supports_impl_scrolling);
 
+  if (!settings_.scroll_animation_duration_for_testing.is_zero()) {
+    mutator_host_->SetScrollAnimationDurationForTesting(
+        settings_.scroll_animation_duration_for_testing);
+  }
+
   std::unique_ptr<LayerTreeHostImpl> host_impl = LayerTreeHostImpl::Create(
       settings_, client, task_runner_provider_.get(),
       rendering_stats_instrumentation_.get(), task_graph_runner_,
@@ -663,7 +668,8 @@
   DCHECK(!settings_.single_thread_proxy_scheduler);
   SingleThreadProxy* proxy = static_cast<SingleThreadProxy*>(proxy_.get());
 
-  proxy->CompositeImmediately(frame_begin_time, raster);
+  proxy->CompositeImmediately(frame_begin_time,
+                              raster || next_commit_forces_redraw_);
 }
 
 bool LayerTreeHost::UpdateLayers() {
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index c1d49e1a..99aaf6d4 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -5492,11 +5492,22 @@
   float scale_factor = active_tree()->page_scale_factor_for_scroll();
   gfx::Vector2dF scaled_delta =
       gfx::ScaleVector2d(scroll_delta, 1.f / scale_factor);
-  return mutator_host_->ImplOnlyScrollAnimationUpdateTarget(
+  bool animation_updated = mutator_host_->ImplOnlyScrollAnimationUpdateTarget(
       scroll_node->element_id, scaled_delta,
       active_tree_->property_trees()->scroll_tree.MaxScrollOffset(
           scroll_node->id),
       CurrentBeginFrameArgs().frame_time, delayed_by);
+  if (animation_updated) {
+    // Because we updated the animation target, notify the SwapPromiseMonitor
+    // to tell it that something happened that will cause a swap in the future.
+    // This will happen within the scope of the dispatch of a gesture scroll
+    // update input event. If we don't notify during the handling of the input
+    // event, the LatencyInfo associated with the input event will not be
+    // added as a swap promise and we won't get any swap results.
+    NotifySwapPromiseMonitorsOfSetNeedsRedraw();
+  }
+
+  return animation_updated;
 }
 
 bool LayerTreeHostImpl::IsElementInList(ElementId element_id,
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 229c385..bff94a6 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -11777,9 +11777,24 @@
   viz::BeginFrameArgs begin_frame_args =
       viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
 
-  EXPECT_EQ(
-      InputHandler::SCROLL_ON_IMPL_THREAD,
-      host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+  {
+    // Creating the animation should set 'needs redraw'. This is required
+    // for LatencyInfo's to be propagated along with the CompositorFrame
+    int set_needs_commit_count = 0;
+    int set_needs_redraw_count = 0;
+    int forward_to_main_count = 0;
+    std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
+        new SimpleSwapPromiseMonitor(
+            nullptr, host_impl_.get(), &set_needs_commit_count,
+            &set_needs_redraw_count, &forward_to_main_count));
+    EXPECT_EQ(
+        InputHandler::SCROLL_ON_IMPL_THREAD,
+        host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+
+    EXPECT_EQ(0, set_needs_commit_count);
+    EXPECT_EQ(1, set_needs_redraw_count);
+    EXPECT_EQ(0, forward_to_main_count);
+  }
 
   LayerImpl* scrolling_layer = host_impl_->OuterViewportScrollLayer();
   EXPECT_EQ(scrolling_layer->scroll_tree_index(),
@@ -11804,10 +11819,26 @@
   float y = scrolling_layer->CurrentScrollOffset().y();
   EXPECT_TRUE(y > 1 && y < 49);
 
-  // Update target.
-  EXPECT_EQ(
-      InputHandler::SCROLL_ON_IMPL_THREAD,
-      host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+  {
+    // Updating the animation should set 'needs redraw'. This is required
+    // for LatencyInfo's to be propagated along with the CompositorFrame
+    int set_needs_commit_count = 0;
+    int set_needs_redraw_count = 0;
+    int forward_to_main_count = 0;
+    std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
+        new SimpleSwapPromiseMonitor(
+            nullptr, host_impl_.get(), &set_needs_commit_count,
+            &set_needs_redraw_count, &forward_to_main_count));
+    // Update target.
+    EXPECT_EQ(
+        InputHandler::SCROLL_ON_IMPL_THREAD,
+        host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
+
+    EXPECT_EQ(0, set_needs_commit_count);
+    EXPECT_EQ(1, set_needs_redraw_count);
+    EXPECT_EQ(0, forward_to_main_count);
+  }
+
   host_impl_->DidFinishImplFrame();
 
   begin_frame_args.frame_time =
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index 63ae9b2..04e4871 100644
--- a/cc/trees/layer_tree_settings.h
+++ b/cc/trees/layer_tree_settings.h
@@ -60,6 +60,7 @@
   bool scrollbar_flash_after_any_scroll_update = false;
   bool scrollbar_flash_when_mouse_enter = false;
   SkColor solid_color_scrollbar_color = SK_ColorWHITE;
+  base::TimeDelta scroll_animation_duration_for_testing;
   bool timeout_and_draw_when_animation_checkerboards = true;
   bool layer_transforms_should_scale_layer_contents = false;
   bool layers_always_allowed_lcd_text = false;
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index 8ff40d8..03aa3fd 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -55,6 +55,8 @@
   virtual void PushPropertiesTo(MutatorHost* host_impl) = 0;
 
   virtual void SetSupportsScrollAnimations(bool supports_scroll_animations) = 0;
+  virtual void SetScrollAnimationDurationForTesting(
+      base::TimeDelta duration) = 0;
   virtual bool NeedsTickAnimations() const = 0;
 
   virtual bool ActivateAnimations() = 0;
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index f267476d..86276e5 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -431,6 +431,19 @@
   ]
 }
 
+# This is a list of all our base module java dependencies. New features should
+# be added to this list.
+java_group("chrome_all_java") {
+  deps = [
+    ":chrome_java",
+    "//chrome/android/features/media_router:java",
+  ]
+
+  if (notouch_build) {
+    deps += [ "//chrome/android/features/touchless:java" ]
+  }
+}
+
 android_library("bundle_canary_java") {
   java_files = [ "//base/android/java/src/org/chromium/base/BundleCanary.java" ]
   deps = [
@@ -1349,8 +1362,7 @@
 java_group("chrome_public_base_module_java") {
   deps = [
     ":app_hooks_java",
-    ":chrome_java",
-    "//chrome/android/features/media_router:java",
+    ":chrome_all_java",
   ]
 }
 
@@ -1527,7 +1539,6 @@
     "//android_webview/support_library:support_lib_glue_java",
     "//base:base_java",
     "//chrome/android:chrome_java",
-    "//chrome/android/features/media_router:java",
     "//content/public/android:content_java",
   ]
   java_files =
@@ -1625,8 +1636,6 @@
       "//android_webview:platform_service_bridge_upstream_implementation_java",
       "//base:base_java",
       "//chrome/android:app_hooks_java",
-      "//chrome/android:chrome_java",
-      "//chrome/android/features/media_router:java",
     ]
 
     add_unwind_tables_in_apk =
@@ -1773,6 +1782,7 @@
     ":chrome_test_apk_template_resources",
     ":chrome_test_java",
     "//chrome/android/features/media_router:test_java",
+    "//chrome/android/features/touchless:touchless_java_test",
     "//chrome/android/webapk/libs/runtime_library:runtime_library_javatests",
     "//chrome/android/webapk/shell_apk:shell_apk_javatests",
     "//chrome/browser/profiling_host:profiling_host_javatests",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index e5ae1c0..ce7e4f9 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -24,9 +24,6 @@
 
   # Enable language splits in the Chrome bundles.
   enable_chrome_language_splits = android_channel == "default"
-
-  # Enable the chrome build for devices without touchscreens.
-  notouch_build = false
 }
 
 default_chrome_public_jinja_variables = [
@@ -235,12 +232,7 @@
       deps += [ ":$_unwind_asset" ]
     }
 
-    # This is a list of all our base module library dependencies. New features
-    # should be added to this list.
-    deps += [
-      "//chrome/android:chrome_java",
-      "//chrome/android/features/media_router:java",
-    ]
+    deps += [ "//chrome/android:chrome_all_java" ]
 
     if (enable_vr && (_target_type == "android_apk" || !modularize_vr)) {
       deps += [ "//chrome/browser/android/vr:java" ]
diff --git a/chrome/android/features/touchless/BUILD.gn b/chrome/android/features/touchless/BUILD.gn
new file mode 100644
index 0000000..b7a485f
--- /dev/null
+++ b/chrome/android/features/touchless/BUILD.gn
@@ -0,0 +1,41 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+
+android_library("java") {
+  java_files =
+      [ "java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java" ]
+
+  deps = [
+    "//base:base_java",
+    "//chrome/android:chrome_java",
+    "//content/public/android:content_java",
+    "//ui/android:ui_java",
+  ]
+}
+
+android_library("touchless_java_test") {
+  testonly = true
+
+  java_files = [ "javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java" ]
+
+  deps = [
+    ":java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//chrome/android:browser_java_test_support",
+    "//chrome/android:chrome_java",
+    "//chrome/android:chrome_test_java",
+    "//chrome/android:chrome_test_util_java",
+    "//chrome/test/android:chrome_java_test_support",
+    "//components/safe_browsing/android:safe_browsing_java",
+    "//content/public/android:content_java",
+    "//content/public/test/android:content_java_test_support",
+    "//net/android:net_java_test_support",
+    "//third_party/android_support_test_runner:runner_java",
+    "//third_party/junit:junit",
+  ]
+}
diff --git a/chrome/android/features/touchless/DEPS b/chrome/android/features/touchless/DEPS
new file mode 100644
index 0000000..d64b6e3
--- /dev/null
+++ b/chrome/android/features/touchless/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+  "+content/public/android/java/src/org/chromium/content_public",
+]
+
+specific_include_rules = {
+  "NoTouchActivityTest\.java": [
+    "+components/safe_browsing",
+  ]
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
rename to chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java b/chrome/android/features/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
similarity index 100%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
rename to chrome/android/features/touchless/javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java
diff --git a/chrome/android/java/res/drawable/selected_tab_background.xml b/chrome/android/java/res/drawable/selected_tab_background.xml
index 5260d9b..6dd969f 100644
--- a/chrome/android/java/res/drawable/selected_tab_background.xml
+++ b/chrome/android/java/res/drawable/selected_tab_background.xml
@@ -5,7 +5,7 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <stroke
-        android:width="3dp"
-        android:color="@color/modern_blue_600" />
+        android:width="2dp"
+        android:color="@color/modern_grey_700" />
     <corners android:radius="@dimen/default_card_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/tab_grid_card_item.xml b/chrome/android/java/res/layout/tab_grid_card_item.xml
index 74847a2..04faf36 100644
--- a/chrome/android/java/res/layout/tab_grid_card_item.xml
+++ b/chrome/android/java/res/layout/tab_grid_card_item.xml
@@ -47,7 +47,7 @@
                 style="@style/HorizontalDivider"
                 android:layout_below="@id/tab_title"/>
     </RelativeLayout>
-    <ImageView
+    <org.chromium.ui.widget.ChromeImageView
         android:id="@+id/close_button"
         android:layout_width="48dp"
         android:layout_height="48dp"
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index d10b2ac2..e041e7cf 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -594,7 +594,7 @@
 
     <!-- Tab List dimensions -->
     <dimen name="tab_grid_favicon_size">32dp</dimen>
-    <dimen name="tab_list_selected_inset">5dp</dimen>
+    <dimen name="tab_list_selected_inset">6dp</dimen>
     <dimen name="bottom_tab_grid_toolbar_icon_size">48dp</dimen>
 
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index 624f0db..bb16d5f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -44,7 +44,6 @@
 import org.chromium.chrome.browser.searchwidget.SearchActivity;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.DocumentModeAssassin;
-import org.chromium.chrome.browser.touchless.NoTouchActivity;
 import org.chromium.chrome.browser.upgrade.UpgradeActivity;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.IntentUtils;
@@ -71,6 +70,9 @@
 
     private static final String TAG = "ActivitiyDispatcher";
 
+    private static final String NO_TOUCH_ACTIVITY_NAME =
+            "org.chromium.chrome.browser.touchless.NoTouchActivity";
+
     /**
      * Timeout in ms for reading PartnerBrowserCustomizations provider. We do not trust third party
      * provider by default.
@@ -435,17 +437,20 @@
         maybePrefetchDnsInBackground();
 
         Intent newIntent = new Intent(mIntent);
-        Class<?> tabbedActivityClass = null;
+        String targetActivityClassName = null;
         if (FeatureUtilities.isNoTouchModeEnabled()) {
             // When in No Touch Mode we don't support tabs, and replace the TabbedActivity with the
             // NoTouchActivity.
-            tabbedActivityClass = NoTouchActivity.class;
+            // We can't depend on NoTouchActivity directly as it's not always compiled in, so
+            // refer to it by string.
+            targetActivityClassName = NO_TOUCH_ACTIVITY_NAME;
         } else {
-            tabbedActivityClass =
-                    MultiWindowUtils.getInstance().getTabbedActivityForIntent(newIntent, mActivity);
+            targetActivityClassName = MultiWindowUtils.getInstance()
+                                              .getTabbedActivityForIntent(newIntent, mActivity)
+                                              .getName();
         }
         newIntent.setClassName(
-                mActivity.getApplicationContext().getPackageName(), tabbedActivityClass.getName());
+                mActivity.getApplicationContext().getPackageName(), targetActivityClassName);
         newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
             newIntent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/BasicNativePage.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/BasicNativePage.java
index 1b9cfa4f..10210ed7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/BasicNativePage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/BasicNativePage.java
@@ -9,7 +9,6 @@
 import android.view.View;
 import android.widget.FrameLayout.LayoutParams;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
@@ -37,10 +36,8 @@
 
         Resources res = mActivity.getResources();
 
-        mTopMargin = 0;
         mBottomMargin = activity.getFullscreenManager().getBottomControlsHeight();
-        mTopMargin = res.getDimensionPixelSize(R.dimen.tab_strip_height)
-                + res.getDimensionPixelSize(R.dimen.toolbar_height_no_shadow);
+        mTopMargin = activity.getFullscreenManager().getTopControlsHeight();
 
         if (host.getActiveTab() != null) host.getActiveTab().addObserver(this);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelFilter.java
index 90a2c66..f2066924 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelFilter.java
@@ -25,6 +25,9 @@
     @Override
     protected void selectTab(Tab tab) {}
 
+    @Override
+    protected void reorder() {}
+
     // TabList implementation.
     @Override
     public boolean isIncognito() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelObserver.java
index 09a8eda..12b55f872 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/EmptyTabModelObserver.java
@@ -47,4 +47,7 @@
 
     @Override
     public void tabRemoved(Tab tab) {}
+
+    @Override
+    public void restoreCompleted() {}
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
index 3d4dd08..d29be3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
@@ -75,7 +75,7 @@
      * @return An unmodifiable list of {@link Tab} that relate with the given tab id.
      */
     @NonNull
-    public List<Tab> getUnmodifiableRelatedTabList(int tabId) {
+    public List<Tab> getRelatedTabList(int tabId) {
         return sEmptyRelatedTabList;
     }
 
@@ -100,6 +100,11 @@
      */
     protected abstract void selectTab(Tab tab);
 
+    /**
+     * Concrete class requires to define the ordering of each Tab within the filter.
+     */
+    protected abstract void reorder();
+
     // TabModelObserver implementation.
     @Override
     public void didSelectTab(Tab tab, int type, int lastId) {
@@ -188,4 +193,13 @@
             observer.tabRemoved(tab);
         }
     }
+
+    @Override
+    public void restoreCompleted() {
+        if (getCount() != 0) reorder();
+
+        for (TabModelObserver observer : mFilteredObservers) {
+            observer.restoreCompleted();
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
index 879d1c5..4d88ff9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
@@ -109,6 +109,12 @@
     }
 
     @Override
+    public void broadcastSessionRestoreComplete() {
+        super.broadcastSessionRestoreComplete();
+        for (TabModelObserver observer : mObservers) observer.restoreCompleted();
+    }
+
+    @Override
     public void addObserver(TabModelObserver observer) {
         mObservers.addObserver(observer);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java
index ba41cd8e..2205ac1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java
@@ -104,4 +104,10 @@
      * @param tab The tab that has been removed.
      */
     void tabRemoved(Tab tab);
+
+    /**
+     * Called after all {@link org.chromium.chrome.browser.tab.TabState}s within {@link TabModel}
+     * are loaded from storage.
+     */
+    void restoreCompleted();
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserverJniBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserverJniBridge.java
index aa6384b..bd67576 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserverJniBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserverJniBridge.java
@@ -108,6 +108,9 @@
         nativeTabRemoved(mNativeTabModelObserverJniBridge, tab);
     }
 
+    @Override
+    public void restoreCompleted() {}
+
     /**
      * Creates an observer bridge for the given tab model. The native counterpart to this object
      * will hold a global reference to the Java endpoint and manage its lifetime. This is private as
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewBinder.java
index 574606b..43f52fa9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewBinder.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.support.annotation.Nullable;
@@ -12,6 +13,7 @@
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.widget.FrameLayout;
 
+import org.chromium.base.Callback;
 import org.chromium.chrome.R;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -58,7 +60,16 @@
         } else if (TabProperties.FAVICON == propertyKey) {
             holder.favicon.setImageBitmap(item.get(TabProperties.FAVICON));
         } else if (TabProperties.THUMBNAIL_FETCHER == propertyKey) {
-            item.get(TabProperties.THUMBNAIL_FETCHER).fetch(holder.thumbnail::setImageBitmap);
+            TabListMediator.ThumbnailFetcher fetcher = item.get(TabProperties.THUMBNAIL_FETCHER);
+            if (fetcher == null) return;
+            Callback<Bitmap> callback = result -> {
+                if (result == null) {
+                    holder.thumbnail.setImageResource(0);
+                } else {
+                    holder.thumbnail.setImageBitmap(result);
+                }
+            };
+            fetcher.fetch(callback);
         } else if (TabProperties.TAB_ID == propertyKey) {
             holder.setTabId(item.get(TabProperties.TAB_ID));
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
index 3d6d2ff..87f9d1e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabGridViewHolder.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -11,6 +12,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 
 /**
@@ -30,6 +32,8 @@
         this.title = itemView.findViewById(R.id.tab_title);
         this.favicon = itemView.findViewById(R.id.tab_favicon);
         this.closeButton = itemView.findViewById(R.id.close_button);
+        DrawableCompat.setTint(this.closeButton.getDrawable(),
+                ApiCompatibilityUtils.getColor(itemView.getResources(), R.color.light_icon_color));
     }
 
     public static TabGridViewHolder create(ViewGroup parent, int itemViewType) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListMediator.java
index 78495845..43293df 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListMediator.java
@@ -44,7 +44,7 @@
      * The object to set to TabProperties.THUMBNAIL_FETCHER for the TabGridViewBinder to obtain
      * the thumbnail asynchronously.
      */
-    class ThumbnailFetcher {
+    static class ThumbnailFetcher {
         private ThumbnailProvider mThumbnailProvider;
         private Tab mTab;
 
@@ -84,7 +84,8 @@
         @Override
         public void run(int tabId) {
             mTabModelSelector.getCurrentModel().closeTab(
-                    TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId));
+                    TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId), false,
+                    false, true);
             RecordUserAction.record("MobileStackViewCloseTab");
         }
     };
@@ -144,14 +145,23 @@
             }
 
             @Override
-            public void didAddTab(Tab tab, int type) {
-                addTabInfoToModel(tab, false);
+            public void tabClosureUndone(Tab tab) {
+                int index = TabModelUtils.getTabIndexById(
+                        mTabModelSelector.getCurrentModel(), tab.getId());
+                addTabInfoToModel(tab, index, mTabModelSelector.getCurrentModel().index() == index);
             }
 
             @Override
-            public void didCloseTab(int tabId, boolean incognito) {
-                if (mModel.indexFromId(tabId) == TabModel.INVALID_TAB_INDEX) return;
-                mModel.removeAt(mModel.indexFromId(tabId));
+            public void didAddTab(Tab tab, int type) {
+                int index = TabModelUtils.getTabIndexById(
+                        mTabModelSelector.getCurrentModel(), tab.getId());
+                addTabInfoToModel(tab, index, mTabModelSelector.getCurrentModel().index() == index);
+            }
+
+            @Override
+            public void willCloseTab(Tab tab, boolean animate) {
+                if (mModel.indexFromId(tab.getId()) == TabModel.INVALID_TAB_INDEX) return;
+                mModel.removeAt(mModel.indexFromId(tab.getId()));
             }
         };
     }
@@ -166,7 +176,7 @@
         }
         int selectedIndex = tabModel.index();
         for (int i = 0; i < tabModel.getCount(); i++) {
-            addTabInfoToModel(tabModel.getTabAt(i), i == selectedIndex);
+            addTabInfoToModel(tabModel.getTabAt(i), i, i == selectedIndex);
         }
     }
 
@@ -183,18 +193,21 @@
         mTabModelObserver.destroy();
     }
 
-    private void addTabInfoToModel(final Tab tab, boolean isSelected) {
+    private void addTabInfoToModel(final Tab tab, int index, boolean isSelected) {
         PropertyModel tabInfo =
                 new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
                         .with(TabProperties.TAB_ID, tab.getId())
                         .with(TabProperties.TITLE, tab.getTitle())
                         .with(TabProperties.FAVICON, tab.getFavicon())
                         .with(TabProperties.IS_SELECTED, isSelected)
-                        .with(TabProperties.THUMBNAIL_FETCHER, null)
                         .with(TabProperties.TAB_SELECTED_LISTENER, mTabSelectedListener)
                         .with(TabProperties.TAB_CLOSED_LISTENER, mTabClosedListener)
                         .build();
-        mModel.add(tabInfo);
+        if (index >= mModel.size()) {
+            mModel.add(tabInfo);
+        } else {
+            mModel.add(index, tabInfo);
+        }
         mFaviconHelper.getLocalFaviconImageForURL(Profile.getLastUsedProfile().getOriginalProfile(),
                 tab.getUrl(), mFaviconSize, (image, iconUrl) -> {
                     if (mModel.indexFromId(tab.getId()) == Tab.INVALID_TAB_ID) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
index f61b42d..89b14cfa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewHolder.java
@@ -4,11 +4,11 @@
 
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
-import android.support.v7.widget.AppCompatImageButton;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageButton;
 
 import org.chromium.chrome.R;
 
@@ -17,7 +17,7 @@
  */
 class TabStripViewHolder extends RecyclerView.ViewHolder {
     public int mTabId;
-    public final AppCompatImageButton button;
+    public final ImageButton button;
 
     public TabStripViewHolder(View itemView) {
         super(itemView);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
new file mode 100644
index 0000000..0ebcbc48
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
@@ -0,0 +1,237 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tasks.tabgroup;
+
+import android.support.annotation.NonNull;
+
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabList;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelFilter;
+import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link TabModelFilter} that puts {@link Tab}s into a group
+ * structure.
+ *
+ * A group is a collection of {@link Tab}s that share a common ancestor {@link Tab}. This filter is
+ * also a {@link TabList} that contains the last shown {@link Tab} from every group.
+ */
+public class TabGroupModelFilter extends TabModelFilter {
+    /**
+     * This class is a representation of a group of tabs. It knows the last selected tab within the
+     * group.
+     */
+    private class TabGroup {
+        private final Set<Integer> mTabIds;
+        private int mLastShownTabId;
+
+        TabGroup() {
+            mTabIds = new LinkedHashSet<>();
+            mLastShownTabId = Tab.INVALID_TAB_ID;
+        }
+
+        void addTab(int tabId) {
+            mTabIds.add(tabId);
+            if (mLastShownTabId == Tab.INVALID_TAB_ID) setLastShownTabId(tabId);
+        }
+
+        void removeTab(int tabId) {
+            assert mTabIds.contains(tabId);
+            if (mLastShownTabId == tabId) {
+                int nextIdToShow = nextTabIdToShow(tabId);
+                if (nextIdToShow != Tab.INVALID_TAB_ID) setLastShownTabId(nextIdToShow);
+            }
+            mTabIds.remove(tabId);
+        }
+
+        void moveToEndInGroup(int tabId) {
+            if (mTabIds.contains(tabId)) mTabIds.remove(tabId);
+            mTabIds.add(tabId);
+        }
+
+        boolean contains(int tabId) {
+            return mTabIds.contains(tabId);
+        }
+
+        int size() {
+            return mTabIds.size();
+        }
+
+        List<Integer> getTabIdList() {
+            return Collections.unmodifiableList(new ArrayList<>(mTabIds));
+        }
+
+        int getLastShownTabId() {
+            return mLastShownTabId;
+        }
+
+        void setLastShownTabId(int tabId) {
+            assert mTabIds.contains(tabId);
+            mLastShownTabId = tabId;
+        }
+
+        int nextTabIdToShow(int tabId) {
+            if (mTabIds.size() == 1 || !mTabIds.contains(tabId)) return Tab.INVALID_TAB_ID;
+            List<Integer> ids = getTabIdList();
+            int position = ids.indexOf(tabId);
+            if (position == 0) return ids.get(position + 1);
+            return ids.get(position - 1);
+        }
+    }
+    private Map<Integer, Integer> mGroupIdToGroupIndexMap = new HashMap<>();
+    private Map<Integer, TabGroup> mGroupIdToGroupMap = new HashMap<>();
+    private int mCurrentGroupIndex = TabList.INVALID_TAB_INDEX;
+
+    public TabGroupModelFilter(TabModel tabModel) {
+        super(tabModel);
+    }
+
+    // TabModelFilter implementation.
+    @NonNull
+    @Override
+    public List<Tab> getRelatedTabList(int id) {
+        // TODO(meiliang): In worst case, this method runs in O(n^2). This method needs to perform
+        // better, especially when we try to call it in a loop for all tabs.
+        Tab tab = TabModelUtils.getTabById(getTabModel(), id);
+        if (tab == null) return super.getRelatedTabList(id);
+
+        int groupId = tab.getRootId();
+        TabGroup group = mGroupIdToGroupMap.get(groupId);
+        return getRelatedTabList(group.getTabIdList());
+    }
+
+    private List<Tab> getRelatedTabList(List<Integer> ids) {
+        List<Tab> tabs = new ArrayList<>();
+        for (Integer id : ids) {
+            tabs.add(TabModelUtils.getTabById(getTabModel(), id));
+        }
+        return Collections.unmodifiableList(tabs);
+    }
+
+    @Override
+    protected void addTab(Tab tab) {
+        if (tab.isIncognito() != isIncognito()) {
+            throw new IllegalStateException("Attempting to open tab in the wrong model");
+        }
+
+        int groupId = tab.getRootId();
+        if (mGroupIdToGroupMap.containsKey(groupId)) {
+            mGroupIdToGroupMap.get(groupId).addTab(tab.getId());
+        } else {
+            TabGroup tabGroup = new TabGroup();
+            tabGroup.addTab(tab.getId());
+            mGroupIdToGroupMap.put(groupId, tabGroup);
+            mGroupIdToGroupIndexMap.put(groupId, mGroupIdToGroupIndexMap.size());
+        }
+    }
+
+    @Override
+    protected void closeTab(Tab tab) {
+        int groupId = tab.getRootId();
+        if (tab.isIncognito() != isIncognito() || mGroupIdToGroupMap.get(groupId) == null
+                || !mGroupIdToGroupMap.get(groupId).contains(tab.getId())) {
+            throw new IllegalStateException("Attempting to close tab in the wrong model");
+        }
+
+        TabGroup group = mGroupIdToGroupMap.get(groupId);
+        group.removeTab(tab.getId());
+        if (group.size() == 0) {
+            updateGroupIdToGroupIndexMapAfterGroupClosed(groupId);
+            mGroupIdToGroupIndexMap.remove(groupId);
+            mGroupIdToGroupMap.remove(groupId);
+        }
+    }
+
+    private void updateGroupIdToGroupIndexMapAfterGroupClosed(int groupId) {
+        int indexToRemove = mGroupIdToGroupIndexMap.get(groupId);
+        Set<Integer> groupIdSet = mGroupIdToGroupIndexMap.keySet();
+        for (Integer groupIdKey : groupIdSet) {
+            int groupIndex = mGroupIdToGroupIndexMap.get(groupIdKey);
+            if (groupIndex > indexToRemove) {
+                mGroupIdToGroupIndexMap.put(groupIdKey, groupIndex - 1);
+            }
+        }
+    }
+
+    @Override
+    protected void selectTab(Tab tab) {
+        int groupId = tab.getRootId();
+        mGroupIdToGroupMap.get(groupId).setLastShownTabId(tab.getId());
+        mCurrentGroupIndex = mGroupIdToGroupIndexMap.get(groupId);
+    }
+
+    @Override
+    protected void reorder() {
+        mGroupIdToGroupIndexMap.clear();
+
+        TabModel tabModel = getTabModel();
+        for (int i = 0; i < tabModel.getCount(); i++) {
+            Tab tab = tabModel.getTabAt(i);
+            int groupId = tab.getRootId();
+            mGroupIdToGroupMap.get(groupId).moveToEndInGroup(tab.getId());
+
+            if (!mGroupIdToGroupIndexMap.containsKey(groupId)) {
+                mGroupIdToGroupIndexMap.put(groupId, mGroupIdToGroupIndexMap.size());
+            }
+        }
+
+        selectTab(tabModel.getTabAt(tabModel.index()));
+
+        assert mGroupIdToGroupIndexMap.size() == mGroupIdToGroupMap.size();
+    }
+
+    // TabList implementation.
+    @Override
+    public boolean isIncognito() {
+        return getTabModel().isIncognito();
+    }
+
+    @Override
+    public int index() {
+        return mCurrentGroupIndex;
+    }
+
+    @Override
+    public int getCount() {
+        return mGroupIdToGroupMap.size();
+    }
+
+    @Override
+    public Tab getTabAt(int index) {
+        if (index < 0 || index >= getCount()) return null;
+        int groupId = Tab.INVALID_TAB_ID;
+        Set<Integer> groupIdSet = mGroupIdToGroupIndexMap.keySet();
+        for (Integer groupIdKey : groupIdSet) {
+            if (mGroupIdToGroupIndexMap.get(groupIdKey) == index) {
+                groupId = groupIdKey;
+                break;
+            }
+        }
+        if (groupId == Tab.INVALID_TAB_ID) return null;
+
+        return TabModelUtils.getTabById(
+                getTabModel(), mGroupIdToGroupMap.get(groupId).getLastShownTabId());
+    }
+
+    @Override
+    public int indexOf(Tab tab) {
+        if (tab == null) return TabList.INVALID_TAB_INDEX;
+        return mGroupIdToGroupIndexMap.get(tab.getId());
+    }
+
+    @Override
+    public boolean isClosurePending(int tabId) {
+        return getTabModel().isClosurePending(tabId);
+    }
+}
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 517fa0f6..73a2be6 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
@@ -127,6 +127,11 @@
             public void tabRemoved(Tab tab) {
                 updateTabCount();
             }
+
+            @Override
+            public void restoreCompleted() {
+                updateTabCount();
+            }
         };
 
         mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 841b7c0..b48752c8 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1653,7 +1653,6 @@
   "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java",
   "java/src/org/chromium/chrome/browser/toolbar/top/ViewShiftingActionBarDelegate.java",
-  "java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingController.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java",
   "java/src/org/chromium/chrome/browser/tracing/TracingNotificationService.java",
@@ -2326,6 +2325,8 @@
   "javatests/src/org/chromium/chrome/browser/tabmodel/TestTabModelDirectory.java",
   "javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java",
   "javatests/src/org/chromium/chrome/browser/tabmodel/document/MockDocumentTabModel.java",
+  "javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListViewHolderTest.java",
+  "javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TestRecyclerViewSimpleViewBinder.java",
   "javatests/src/org/chromium/chrome/browser/test/ChromeBrowserTestRule.java",
   "javatests/src/org/chromium/chrome/browser/test/ClearAppDataTestRule.java",
   "javatests/src/org/chromium/chrome/browser/test/CommandLineInitRule.java",
@@ -2333,7 +2334,6 @@
   "javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java",
   "javatests/src/org/chromium/chrome/browser/toolbar/ToolbarTest.java",
   "javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java",
-  "javatests/src/org/chromium/chrome/browser/touchless/NoTouchActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java",
   "javatests/src/org/chromium/chrome/browser/translate/TranslateOptionsTest.java",
   "javatests/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListViewHolderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListViewHolderTest.java
new file mode 100644
index 0000000..26ba2e31
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabListViewHolderTest.java
@@ -0,0 +1,202 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tasks.tab_list_ui;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ui.DummyUiActivity;
+import org.chromium.chrome.test.ui.DummyUiActivityTestCase;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests for the {@link android.support.v7.widget.RecyclerView.ViewHolder} classes for {@link
+ * TabListCoordinator}.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class TabListViewHolderTest extends DummyUiActivityTestCase {
+    private TabGridViewHolder mTabGridViewHolder;
+    private PropertyModel mGridModel;
+    private PropertyModelChangeProcessor mGridMCP;
+
+    private TabStripViewHolder mTabStripViewHolder;
+    private PropertyModel mStripModel;
+    private PropertyModelChangeProcessor mStripMCP;
+
+    private TabListMediator.ThumbnailFetcher mMockThumbnailProvider =
+            new TabListMediator.ThumbnailFetcher(new TabListMediator.ThumbnailProvider() {
+                @Override
+                public void getTabThumbnailWithCallback(Tab tab, Callback<Bitmap> callback) {
+                    Bitmap bitmap = mShouldReturnBitmap
+                            ? Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+                            : null;
+                    callback.onResult(bitmap);
+                }
+            }, null);
+
+    private TabListMediator.TabActionListener mMockCloseListener =
+            new TabListMediator.TabActionListener() {
+                @Override
+                public void run(int tabId) {
+                    mCloseClicked.set(true);
+                }
+            };
+    private AtomicBoolean mCloseClicked = new AtomicBoolean();
+
+    private TabListMediator.TabActionListener mMockSelectedListener =
+            new TabListMediator.TabActionListener() {
+                @Override
+                public void run(int tabId) {
+                    mSelectClicked.set(true);
+                }
+            };
+    private AtomicBoolean mSelectClicked = new AtomicBoolean();
+    private boolean mShouldReturnBitmap;
+
+    @BeforeClass
+    public static void setUpBeforeActivityLaunched() {
+        DummyUiActivity.setTestTheme(R.style.Theme_Chromium_Activity_Fullscreen);
+    }
+
+    @Override
+    public void setUpTest() throws Exception {
+        super.setUpTest();
+        ViewGroup view = new LinearLayout(getActivity());
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            getActivity().setContentView(view, params);
+
+            mTabGridViewHolder = TabGridViewHolder.create(view, 0);
+            mTabStripViewHolder = TabStripViewHolder.create(view, 0);
+
+            view.addView(mTabGridViewHolder.itemView);
+            view.addView(mTabStripViewHolder.itemView);
+        });
+
+        mGridModel = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
+                             .with(TabProperties.TAB_SELECTED_LISTENER, mMockSelectedListener)
+                             .with(TabProperties.TAB_CLOSED_LISTENER, mMockCloseListener)
+                             .build();
+        mStripModel = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_STRIP)
+                              .with(TabProperties.TAB_SELECTED_LISTENER, mMockSelectedListener)
+                              .with(TabProperties.TAB_CLOSED_LISTENER, mMockCloseListener)
+                              .build();
+
+        mGridMCP = PropertyModelChangeProcessor.create(mGridModel, mTabGridViewHolder,
+                new TestRecyclerViewSimpleViewBinder<>(TabGridViewBinder::onBindViewHolder));
+        mStripMCP = PropertyModelChangeProcessor.create(mStripModel, mTabStripViewHolder,
+                new TestRecyclerViewSimpleViewBinder<>(TabStripViewBinder::onBindViewHolder));
+    }
+
+    @Test
+    @MediumTest
+    @UiThreadTest
+    public void testSelected() throws Exception {
+        mGridModel.set(TabProperties.IS_SELECTED, true);
+        Assert.assertTrue(((FrameLayout) (mTabGridViewHolder.itemView)).getForeground() != null);
+        mGridModel.set(TabProperties.IS_SELECTED, false);
+        Assert.assertFalse(((FrameLayout) (mTabGridViewHolder.itemView)).getForeground() != null);
+
+        mStripModel.set(TabProperties.IS_SELECTED, true);
+        Assert.assertTrue(((FrameLayout) (mTabStripViewHolder.itemView)).getForeground() != null);
+        mStripModel.set(TabProperties.IS_SELECTED, false);
+        Assert.assertFalse(((FrameLayout) (mTabStripViewHolder.itemView)).getForeground() != null);
+    }
+
+    @Test
+    @MediumTest
+    @UiThreadTest
+    public void testTitle() throws Exception {
+        final String title = "Surf the cool webz";
+        mGridModel.set(TabProperties.TITLE, title);
+        Assert.assertEquals(mTabGridViewHolder.title.getText(), title);
+    }
+
+    @Test
+    @MediumTest
+    @UiThreadTest
+    public void testThumbnail() throws Exception {
+        mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
+        // This should have set the image resource id to 0 and reset it.
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+            Assert.assertNull(mTabGridViewHolder.thumbnail.getDrawable());
+        } else {
+            assertThat(mTabGridViewHolder.thumbnail.getDrawable(), instanceOf(ColorDrawable.class));
+        }
+        mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
+
+        mShouldReturnBitmap = true;
+        mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
+        assertThat(mTabGridViewHolder.thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
+    }
+
+    @Test
+    @MediumTest
+    @UiThreadTest
+    public void testClickToSelect() throws Exception {
+        mTabGridViewHolder.itemView.performClick();
+        Assert.assertTrue(mSelectClicked.get());
+        mSelectClicked.set(false);
+
+        mStripModel.set(TabProperties.IS_SELECTED, false);
+        mTabStripViewHolder.button.performClick();
+        Assert.assertTrue(mSelectClicked.get());
+        mSelectClicked.set(false);
+
+        mStripModel.set(TabProperties.IS_SELECTED, true);
+        mTabStripViewHolder.button.performClick();
+        Assert.assertFalse(mSelectClicked.get());
+    }
+
+    @Test
+    @MediumTest
+    @UiThreadTest
+    public void testClickToClose() throws Exception {
+        mTabGridViewHolder.closeButton.performClick();
+        Assert.assertTrue(mCloseClicked.get());
+        mCloseClicked.set(false);
+
+        mStripModel.set(TabProperties.IS_SELECTED, true);
+        mTabStripViewHolder.button.performClick();
+        Assert.assertTrue(mCloseClicked.get());
+        mCloseClicked.set(false);
+
+        mStripModel.set(TabProperties.IS_SELECTED, false);
+        mTabStripViewHolder.button.performClick();
+        Assert.assertFalse(mCloseClicked.get());
+    }
+
+    @Override
+    public void tearDownTest() throws Exception {
+        mStripMCP.destroy();
+        mGridMCP.destroy();
+        super.tearDownTest();
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TestRecyclerViewSimpleViewBinder.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TestRecyclerViewSimpleViewBinder.java
new file mode 100644
index 0000000..92c2ded
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tasks/tab_list_ui/TestRecyclerViewSimpleViewBinder.java
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tasks.tab_list_ui;
+
+import android.support.v7.widget.RecyclerView;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.modelutil.SimpleRecyclerViewMcpBase;
+
+/**
+ * Test utility class to allow using {@link
+ * org.chromium.ui.modelutil.SimpleRecyclerViewMcpBase.ViewBinder} classes to be used in conjunction
+ * with a {@link PropertyModelChangeProcessor} so that individual items in the RecyclerView can be
+ * tested independently.
+ * @param <VH> The ViewHolder class to be used.
+ */
+public class TestRecyclerViewSimpleViewBinder<VH extends RecyclerView.ViewHolder>
+        implements PropertyModelChangeProcessor.ViewBinder<PropertyModel, VH, PropertyKey> {
+    SimpleRecyclerViewMcpBase.ViewBinder<PropertyModel, VH, PropertyKey> mInternalViewBinder;
+
+    /**
+     * Main constructor
+     * @param viewBinder The {@link org.chromium.ui.modelutil.SimpleRecyclerViewMcpBase.ViewBinder}
+     *         to wrap around.
+     */
+    TestRecyclerViewSimpleViewBinder(
+            SimpleRecyclerViewMcpBase.ViewBinder<PropertyModel, VH, PropertyKey> viewBinder) {
+        mInternalViewBinder = viewBinder;
+    }
+
+    @Override
+    public void bind(PropertyModel model, VH viewHolder, PropertyKey propertyKey) {
+        mInternalViewBinder.onBindViewHolder(viewHolder, model, propertyKey);
+    }
+}
\ No newline at end of file
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 365545f9..8268806 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -924,10 +924,10 @@
 
       <if expr="not is_android">
         <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the accessibility labels service of Google to Chromium.">
-          If an image is missing a description, Chromium will try to provide a description for you. Images are scanned by Google. You can turn this off in settings at any time.
+          If an image doesn’t have a useful description, Chromium will try to provide one for you. Images are scanned by Google. You can turn this off in settings at any time.
         </message>
         <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_BUBBLE_TEXT_ONCE" desc="The text of a bubble that confirms users allows integrating the accessibility labels service of Google to Chromium just once.">
-          If an image is missing a description, Chromium will try to provide a description for you. Images are scanned by Google.
+          If an image doesn’t have a useful description, Chromium will try to provide one for you. Images are scanned by Google.
         </message>
         <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the spelling service of Google to Chrome.">
           Chromium can provide smarter spell-checking by sending what you type in the browser to Google servers, allowing you to use the same spell-checking technology used by Google search.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 4b80f5d..bc71077 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -784,7 +784,7 @@
             &amp;Add to Dictionary
           </message>
           <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_MENU_OPTION" desc="The context-menu item that asks whether to integrate the accessibility image labeling service of Google to Chrome. This text is also used as the title of a bubble which confirms it.">
-            Get Image Descriptions From Google
+            Get Image Descriptions from Google
           </message>
           <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_SEND" desc="The context-menu sub-item that asks whether to integrate the accessibility image labeling service of Google to Chrome always.">
             Always
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 4a98bff..9175b6b 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -942,10 +942,10 @@
       <!-- content area context menus. Android does not use it -->
       <if expr="not is_android">
         <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the accessibility labels service of Google to Chromium.">
-          If an image is missing a description, Google Chrome will try to provide a description for you. Images are scanned by Google. You can turn this off in settings at any time.
+          If an image doesn’t have a useful description, Chrome will try to provide one for you. Images are scanned by Google. You can turn this off in settings at any time.
         </message>
         <message name="IDS_CONTENT_CONTEXT_ACCESSIBILITY_LABELS_BUBBLE_TEXT_ONCE" desc="The text of a bubble that confirms users allows integrating the accessibility labels service of Google to Chromium just once.">
-          If an image is missing a description, Google Chrome will try to provide a description for you. Images are scanned by Google.
+          If an image doesn’t have a useful description, Chrome will try to provide one for you. Images are scanned by Google.
         </message>
         <message name="IDS_CONTENT_CONTEXT_SPELLING_BUBBLE_TEXT" desc="The text of a bubble that confirms users allows integrating the spelling service of Google to Chrome.">
           Google Chrome can provide smarter spell-checking by sending what you type in the browser to Google servers, allowing you to use the same spell-checking technology used by Google search.
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 4ada9a0..189a8dd 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -175,10 +175,10 @@
     Enable accessibility features
   </message>
   <message name="IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_TITLE" desc="Description for screen reader image labels feature.">
-    Image labeling
+    Get image descriptions from Google
   </message>
   <message name="IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_SUBTITLE" desc="Subtitle for screen reader image labels feature.">
-    Send images to Google servers to get labels.
+    If an image doesn’t have a useful description, Chrome will provide one for you. Images are scanned by Google.
   </message>
   <if expr="chromeos">
     <message name="IDS_SETTINGS_OPTIONS_IN_MENU_LABEL" desc="Label for checkbox which enables showing accessibility options in the system menu.">
diff --git a/chrome/app/shutdown_signal_handlers_posix.cc b/chrome/app/shutdown_signal_handlers_posix.cc
index 4b487d7..621d441 100644
--- a/chrome/app/shutdown_signal_handlers_posix.cc
+++ b/chrome/app/shutdown_signal_handlers_posix.cc
@@ -9,6 +9,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <utility>
+
 #include "base/callback.h"
 #include "base/debug/leak_annotations.h"
 #include "base/logging.h"
@@ -74,7 +76,7 @@
  public:
   ShutdownDetector(
       int shutdown_fd,
-      const base::Closure& shutdown_callback,
+      base::OnceCallback<void(int)> shutdown_callback,
       const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
   ~ShutdownDetector() override;
 
@@ -83,7 +85,7 @@
 
  private:
   const int shutdown_fd_;
-  const base::Closure shutdown_callback_;
+  base::OnceCallback<void(int)> shutdown_callback_;
   const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   DISALLOW_COPY_AND_ASSIGN(ShutdownDetector);
@@ -91,13 +93,13 @@
 
 ShutdownDetector::ShutdownDetector(
     int shutdown_fd,
-    const base::Closure& shutdown_callback,
+    base::OnceCallback<void(int)> shutdown_callback,
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
     : shutdown_fd_(shutdown_fd),
-      shutdown_callback_(shutdown_callback),
+      shutdown_callback_(std::move(shutdown_callback)),
       task_runner_(task_runner) {
   CHECK_NE(shutdown_fd_, -1);
-  CHECK(!shutdown_callback.is_null());
+  CHECK(!shutdown_callback_.is_null());
   CHECK(task_runner_);
 }
 
@@ -146,7 +148,8 @@
   } while (bytes_read < sizeof(signal));
   VLOG(1) << "Handling shutdown for signal " << signal << ".";
 
-  if (!task_runner_->PostTask(FROM_HERE, shutdown_callback_)) {
+  if (!task_runner_->PostTask(
+          FROM_HERE, base::BindOnce(std::move(shutdown_callback_), signal))) {
     // Without a valid task runner to post the exit task to, there aren't many
     // options. Raise the signal again. The default handler will pick it up
     // and cause an ungraceful exit.
@@ -172,7 +175,7 @@
 }  // namespace
 
 void InstallShutdownSignalHandlers(
-    const base::Closure& shutdown_callback,
+    base::OnceCallback<void(int)> shutdown_callback,
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
   int pipefd[2];
   int ret = pipe(pipefd);
@@ -191,7 +194,7 @@
   const size_t kShutdownDetectorThreadStackSize = PTHREAD_STACK_MIN * 4;
 #endif
   ShutdownDetector* detector = new ShutdownDetector(
-      g_shutdown_pipe_read_fd, shutdown_callback, task_runner);
+      g_shutdown_pipe_read_fd, std::move(shutdown_callback), task_runner);
   // PlatformThread does not delete its delegate.
   ANNOTATE_LEAKING_OBJECT_PTR(detector);
   if (!base::PlatformThread::CreateNonJoinable(kShutdownDetectorThreadStackSize,
@@ -211,15 +214,15 @@
   struct sigaction action;
   memset(&action, 0, sizeof(action));
   action.sa_handler = SIGTERMHandler;
-  CHECK(sigaction(SIGTERM, &action, nullptr) == 0);
+  CHECK_EQ(0, sigaction(SIGTERM, &action, nullptr));
 
   // Also handle SIGINT - when the user terminates the browser via Ctrl+C. If
   // the browser process is being debugged, GDB will catch the SIGINT first.
   action.sa_handler = SIGINTHandler;
-  CHECK(sigaction(SIGINT, &action, nullptr) == 0);
+  CHECK_EQ(0, sigaction(SIGINT, &action, nullptr));
 
   // And SIGHUP, for when the terminal disappears. On shutdown, many Linux
   // distros send SIGHUP, SIGTERM, and then SIGKILL.
   action.sa_handler = SIGHUPHandler;
-  CHECK(sigaction(SIGHUP, &action, nullptr) == 0);
+  CHECK_EQ(0, sigaction(SIGHUP, &action, nullptr));
 }
diff --git a/chrome/app/shutdown_signal_handlers_posix.h b/chrome/app/shutdown_signal_handlers_posix.h
index 4a9384c2..0724a74 100644
--- a/chrome/app/shutdown_signal_handlers_posix.h
+++ b/chrome/app/shutdown_signal_handlers_posix.h
@@ -16,7 +16,7 @@
 // signals like SIGTERM, SIGINT and SIGTERM. |shutdown_callback| is invoked on
 // |task_runner| which is usually the main thread's task runner.
 void InstallShutdownSignalHandlers(
-    const base::Closure& shutdown_callback,
+    base::OnceCallback<void(int)> shutdown_callback,
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
 
 #endif  // CHROME_APP_SHUTDOWN_SIGNAL_HANDLERS_POSIX_H_
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 4593eac..f5ed378 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -850,8 +850,9 @@
     "net/chrome_url_request_context_getter.h",
     "net/dns_probe_runner.cc",
     "net/dns_probe_runner.h",
-    "net/dns_probe_service.cc",
     "net/dns_probe_service.h",
+    "net/dns_probe_service_factory.cc",
+    "net/dns_probe_service_factory.h",
     "net/failing_url_request_interceptor.cc",
     "net/failing_url_request_interceptor.h",
     "net/file_downloader.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index af0e72a..b529f7c0 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1982,9 +1982,6 @@
      flag_descriptions::kMacViewsTaskManagerDescription, kOsMac,
      FEATURE_VALUE_TYPE(features::kViewsTaskManager)},
 #endif  // OS_MACOSX
-    {"enable-gamepad-vibration", flag_descriptions::kGamepadVibrationName,
-     flag_descriptions::kGamepadVibrationDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kGamepadVibration)},
     {"enable-webvr", flag_descriptions::kWebvrName,
      flag_descriptions::kWebvrDescription, kOsAll,
      SINGLE_VALUE_TYPE(switches::kEnableWebVR)},
@@ -2589,6 +2586,13 @@
      kOsDesktop,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillLocalCardMigrationShowFeedback)},
+    {"enable-autofill-local-card-migration-uses-strike-system-v2",
+     flag_descriptions::kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Name,
+     flag_descriptions::
+         kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Description,
+     kOsAll,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillLocalCardMigrationUsesStrikeSystemV2)},
     {"enable-autofill-save-card-dialog-unlabeled-expiration-date",
      flag_descriptions::
          kEnableAutofillSaveCardDialogUnlabeledExpirationDateName,
diff --git a/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc
index f50fedb..3f389b3 100644
--- a/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc
+++ b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.cc
@@ -65,12 +65,12 @@
   return reinterpret_cast<intptr_t>(send_tab_to_self_android_bridge);
 }
 
-void SendTabToSelfAndroidBridge::destroy(JNIEnv*,
+void SendTabToSelfAndroidBridge::Destroy(JNIEnv*,
                                          const JavaParamRef<jobject>&) {
   delete this;
 }
 
-void SendTabToSelfAndroidBridge::getAllGuids(
+void SendTabToSelfAndroidBridge::GetAllGuids(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& j_guid_list_obj) {
@@ -86,13 +86,13 @@
   }
 }
 
-void SendTabToSelfAndroidBridge::deleteAllEntries(
+void SendTabToSelfAndroidBridge::DeleteAllEntries(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj) {
   send_tab_to_self_model_->DeleteAllEntries();
 }
 
-ScopedJavaLocalRef<jobject> SendTabToSelfAndroidBridge::addEntry(
+ScopedJavaLocalRef<jobject> SendTabToSelfAndroidBridge::AddEntry(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& j_url,
@@ -111,7 +111,7 @@
   return CreateJavaSendTabToSelfEntry(env, persisted_entry);
 }
 
-ScopedJavaLocalRef<jobject> SendTabToSelfAndroidBridge::getEntryByGUID(
+ScopedJavaLocalRef<jobject> SendTabToSelfAndroidBridge::GetEntryByGUID(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jstring>& j_guid) {
diff --git a/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.h b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.h
index 083aec05..804a4f5 100644
--- a/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.h
+++ b/chrome/browser/android/send_tab_to_self/send_tab_to_self_android_bridge.h
@@ -22,23 +22,23 @@
                              const base::android::JavaRef<jobject>& obj,
                              const base::android::JavaRef<jobject>& j_profile);
 
-  void destroy(JNIEnv*, const base::android::JavaParamRef<jobject>&);
+  void Destroy(JNIEnv*, const base::android::JavaParamRef<jobject>&);
 
   // Populates a list of GUIDs in the model.
-  void getAllGuids(JNIEnv* env,
+  void GetAllGuids(JNIEnv* env,
                    const base::android::JavaParamRef<jobject>& obj,
                    const base::android::JavaParamRef<jobject>& j_guid_list_obj);
 
   // Returns the entry associated with a GUID. May return nullptr if none is
   // found.
-  base::android::ScopedJavaLocalRef<jobject> getEntryByGUID(
+  base::android::ScopedJavaLocalRef<jobject> GetEntryByGUID(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jstring>& j_guid);
 
   // Adds a new entry with the specified parameters. Returns the persisted
   // version which contains additional information such as GUID.
-  base::android::ScopedJavaLocalRef<jobject> addEntry(
+  base::android::ScopedJavaLocalRef<jobject> AddEntry(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jstring>& j_url,
@@ -46,7 +46,7 @@
       jlong j_navigation_time);
 
   // Deletes all entries in the model.
-  void deleteAllEntries(JNIEnv* env,
+  void DeleteAllEntries(JNIEnv* env,
                         const base::android::JavaParamRef<jobject>& obj);
 
  protected:
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index 81df4b59..5876133 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -67,11 +67,10 @@
   if (host == chrome::kChromeUISyncHost) {
     // Replace sync with sync-internals (for legacy reasons).
     host = chrome::kChromeUISyncInternalsHost;
-// Redirect chrome://extensions, chrome://extensions-frame, and
-// chrome://settings/extensions all to chrome://extensions and forward path.
+// Redirect chrome://extensions and chrome://settings/extensions all to
+// chrome://extensions and forward path.
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   } else if (host == chrome::kChromeUIExtensionsHost ||
-             host == chrome::kChromeUIExtensionsFrameHost ||
              (host == chrome::kChromeUISettingsHost &&
               url->path() ==
                   std::string("/") + chrome::kDeprecatedExtensionsSubPage)) {
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index a02d88f..bf05e2f 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -159,8 +159,7 @@
       <include name="IDR_UKM_INTERNALS_JS" file="../../components/ukm/debug/ukm_internals.js" flattenhtml="true" compress="gzip" type="BINDATA" />
       <include name="IDR_UKM_INTERNALS_CSS" file="../../components/ukm/debug/ukm_internals.css" flattenhtml="true" compress="gzip" type="BINDATA" />
       <if expr="not is_android">
-        <include name="IDR_DOWNLOADS_IMAGES_1X_INCOGNITO_MARKER_PNG" file="resources\downloads\images\1x\incognito_marker.png" type="BINDATA" />
-        <include name="IDR_DOWNLOADS_IMAGES_2X_INCOGNITO_MARKER_PNG" file="resources\downloads\images\2x\incognito_marker.png" type="BINDATA" />
+        <include name="IDR_DOWNLOADS_IMAGES_INCOGNITO_MARKER_SVG" file="resources\downloads\images\incognito_marker.svg" type="BINDATA" />
         <include name="IDR_DOWNLOADS_IMAGES_NO_DOWNLOADS_SVG" file="resources\downloads\images\no_downloads.svg" type="BINDATA" />
         <include name="IDR_DOWNLOADS_MOJO_LITE_JS" file="${root_gen_dir}\chrome\browser\ui\webui\downloads\downloads.mojom-lite.js" use_base_dir="false" type="BINDATA" />
         <if expr="optimize_webui">
diff --git a/chrome/browser/chrome_browser_main_posix.cc b/chrome/browser/chrome_browser_main_posix.cc
index 5851367b..769459f 100644
--- a/chrome/browser/chrome_browser_main_posix.cc
+++ b/chrome/browser/chrome_browser_main_posix.cc
@@ -40,7 +40,7 @@
 class ExitHandler {
  public:
   // Invokes exit when appropriate.
-  static void ExitWhenPossibleOnUIThread();
+  static void ExitWhenPossibleOnUIThread(int signal);
 
  private:
   ExitHandler();
@@ -64,13 +64,38 @@
 };
 
 // static
-void ExitHandler::ExitWhenPossibleOnUIThread() {
+void ExitHandler::ExitWhenPossibleOnUIThread(int signal) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (SessionRestore::IsRestoringSynchronously()) {
     // ExitHandler takes care of deleting itself.
     new ExitHandler();
   } else {
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+    switch (signal) {
+      case SIGINT:
+      case SIGHUP:
+        // SIGINT gets sent when the user types Ctrl+C, but the session is
+        // likely not going away, so try to exit gracefully.  SIGHUP is sent on
+        // most systems as a first warning of shutdown.  If the process takes
+        // too long to quit, the next signal is usually SIGTERM.
+        Exit();
+        break;
+      case SIGTERM:
+        // SIGTERM is usually sent instead of SIGKILL to gracefully shutdown
+        // processes.  But most systems use it as a shutdown warning, so
+        // conservatively assume that the session is ending.  If the process
+        // still doesn't quit within a bounded time, most systems will finally
+        // send SIGKILL, which we're unable to install a signal handler for.
+        // TODO(thomasanderson): Try to distinguish if the session is really
+        // ending or not.  Maybe there's a systemd or DBus API to query.
+        chrome::SessionEnding();
+        break;
+      default:
+        NOTREACHED();
+    }
+#else
     Exit();
+#endif
   }
 }
 
@@ -125,7 +150,7 @@
   struct sigaction action;
   memset(&action, 0, sizeof(action));
   action.sa_handler = SIGCHLDHandler;
-  CHECK(sigaction(SIGCHLD, &action, NULL) == 0);
+  CHECK_EQ(0, sigaction(SIGCHLD, &action, NULL));
 
   return service_manager::RESULT_CODE_NORMAL_EXIT;
 }
@@ -135,7 +160,7 @@
 
   // Exit in response to SIGINT, SIGTERM, etc.
   InstallShutdownSignalHandlers(
-      base::Bind(&ExitHandler::ExitWhenPossibleOnUIThread),
+      base::BindOnce(&ExitHandler::ExitWhenPossibleOnUIThread),
       base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}));
 }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index ec96d43..40a9129 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1744,6 +1744,12 @@
     "preferences.h",
     "prefs/pref_connector_service.cc",
     "prefs/pref_connector_service.h",
+    "printing/bulk_printers_calculator.cc",
+    "printing/bulk_printers_calculator.h",
+    "printing/bulk_printers_calculator_factory.cc",
+    "printing/bulk_printers_calculator_factory.h",
+    "printing/calculators_policies_binder.cc",
+    "printing/calculators_policies_binder.h",
     "printing/cups_print_job.cc",
     "printing/cups_print_job.h",
     "printing/cups_print_job_manager.cc",
@@ -1758,17 +1764,8 @@
     "printing/cups_printers_manager.h",
     "printing/cups_printers_manager_factory.cc",
     "printing/cups_printers_manager_factory.h",
-    "printing/device_external_printers_factory.cc",
-    "printing/device_external_printers_factory.h",
-    "printing/device_external_printers_settings_bridge.cc",
-    "printing/device_external_printers_settings_bridge.h",
-    "printing/external_printers.cc",
-    "printing/external_printers.h",
-    "printing/external_printers_factory.cc",
-    "printing/external_printers_factory.h",
-    "printing/external_printers_policies.h",
-    "printing/external_printers_pref_bridge.cc",
-    "printing/external_printers_pref_bridge.h",
+    "printing/enterprise_printers_provider.cc",
+    "printing/enterprise_printers_provider.h",
     "printing/ppd_provider_factory.cc",
     "printing/ppd_provider_factory.h",
     "printing/printer_configurer.cc",
@@ -2464,8 +2461,8 @@
     "power/process_data_collector_unittest.cc",
     "power/renderer_freezer_unittest.cc",
     "preferences_unittest.cc",
+    "printing/bulk_printers_calculator_unittest.cc",
     "printing/cups_printers_manager_unittest.cc",
-    "printing/external_printers_unittest.cc",
     "printing/printer_detector_test_util.h",
     "printing/printer_event_tracker_unittest.cc",
     "printing/printers_sync_bridge_unittest.cc",
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 7821399..bc5770c 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -224,9 +224,7 @@
         new NetworkPortalDetectorStub());
   } else {
     network_portal_detector::SetNetworkPortalDetector(
-        new NetworkPortalDetectorImpl(
-            g_browser_process->system_network_context_manager()
-                ->GetURLLoaderFactory()));
+        new NetworkPortalDetectorImpl());
   }
 }
 
diff --git a/chrome/browser/chromeos/cryptauth/client_app_metadata_provider_service.cc b/chrome/browser/chromeos/cryptauth/client_app_metadata_provider_service.cc
index 87f37c88..1f4777d8 100644
--- a/chrome/browser/chromeos/cryptauth/client_app_metadata_provider_service.cc
+++ b/chrome/browser/chromeos/cryptauth/client_app_metadata_provider_service.cc
@@ -34,7 +34,7 @@
 
 namespace {
 
-const char kInstanceIdScope[] = "*";
+const char kInstanceIdScope[] = "GCM";
 
 const cryptauthv2::FeatureMetadata& GenerateFeatureMetadata() {
   static const base::NoDestructor<cryptauthv2::FeatureMetadata>
@@ -207,7 +207,8 @@
     const std::string& instance_id) {
   DCHECK(!instance_id.empty());
   instance_id_->GetToken(
-      device_sync::kCryptAuthGcmProjectId /* authorized_entity */,
+      device_sync::
+          kCryptAuthGcmInstanceIdAuthorizedEntity /* authorized_entity */,
       kInstanceIdScope /* scope */,
       std::map<std::string, std::string>() /* options */, false /* is_lazy */,
       base::Bind(&ClientAppMetadataProviderService::OnInstanceIdTokenFetched,
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 348a006..4c24ad3 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -1065,32 +1065,69 @@
 // AutotestPrivateGetPrinterListFunction
 ///////////////////////////////////////////////////////////////////////////////
 
+AutotestPrivateGetPrinterListFunction::AutotestPrivateGetPrinterListFunction()
+    : results_(std::make_unique<base::Value>(base::Value::Type::LIST)) {}
+
 AutotestPrivateGetPrinterListFunction::
-    ~AutotestPrivateGetPrinterListFunction() = default;
+    ~AutotestPrivateGetPrinterListFunction() {
+  printers_manager_->RemoveObserver(this);
+}
 
 ExtensionFunction::ResponseAction AutotestPrivateGetPrinterListFunction::Run() {
   DVLOG(1) << "AutotestPrivateGetPrinterListFunction";
 
-  auto values = std::make_unique<base::ListValue>();
   Profile* profile = Profile::FromBrowserContext(browser_context());
-  std::unique_ptr<chromeos::CupsPrintersManager> printers_manager =
-      chromeos::CupsPrintersManager::Create(profile);
+  printers_manager_ = chromeos::CupsPrintersManager::Create(profile);
+  printers_manager_->AddObserver(this);
+
+  // Set up a timer to finish waiting after 10 seconds
+  timeout_timer_.Start(
+      FROM_HERE, base::TimeDelta::FromSeconds(10),
+      base::BindOnce(
+          &AutotestPrivateGetPrinterListFunction::RespondWithTimeoutError,
+          this));
+
+  return RespondLater();
+}
+
+void AutotestPrivateGetPrinterListFunction::RespondWithTimeoutError() {
+  if (did_respond())
+    return;
+  Respond(Error("Timeout occured before Enterprise printers were initialized"));
+}
+
+void AutotestPrivateGetPrinterListFunction::RespondWithSuccess() {
+  if (did_respond())
+    return;
+  Respond(OneArgument(std::move(results_)));
+}
+
+void AutotestPrivateGetPrinterListFunction::OnEnterprisePrintersInitialized() {
+  // We are ready to get the list of printers and finish.
   std::vector<chromeos::CupsPrintersManager::PrinterClass> printer_type = {
       chromeos::CupsPrintersManager::PrinterClass::kConfigured,
       chromeos::CupsPrintersManager::PrinterClass::kEnterprise,
       chromeos::CupsPrintersManager::PrinterClass::kAutomatic};
+  base::Value::ListStorage& vresults = results_->GetList();
   for (const auto& type : printer_type) {
     std::vector<chromeos::Printer> printer_list =
-        printers_manager->GetPrinters(type);
+        printers_manager_->GetPrinters(type);
     for (const auto& printer : printer_list) {
-      auto result = std::make_unique<base::DictionaryValue>();
-      result->SetString("printerName", printer.display_name());
-      result->SetString("printerId", printer.id());
-      result->SetString("printerType", GetPrinterType(type));
-      values->Append(std::move(result));
+      vresults.push_back(base::Value(base::Value::Type::DICTIONARY));
+      base::Value& result = vresults.back();
+      result.SetKey("printerName", base::Value(printer.display_name()));
+      result.SetKey("printerId", base::Value(printer.id()));
+      result.SetKey("printerType", base::Value(GetPrinterType(type)));
     }
   }
-  return RespondNow(OneArgument(std::move(values)));
+  // We have to respond in separate task, because it will cause a destruction of
+  // CupsPrintersManager
+  const bool posted = PostTask(
+      FROM_HERE,
+      base::BindOnce(&AutotestPrivateGetPrinterListFunction::RespondWithSuccess,
+                     this));
+  if (posted)
+    timeout_timer_.AbandonAndStop();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1496,6 +1533,33 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// AutotestPrivateSetTabletModeEnabledFunction
+///////////////////////////////////////////////////////////////////////////////
+
+AutotestPrivateSetTabletModeEnabledFunction::
+    ~AutotestPrivateSetTabletModeEnabledFunction() = default;
+
+ExtensionFunction::ResponseAction
+AutotestPrivateSetTabletModeEnabledFunction::Run() {
+  DVLOG(1) << "AutotestPrivateSetTabletModeEnabledFunction";
+
+  std::unique_ptr<api::autotest_private::SetTabletModeEnabled::Params> params(
+      api::autotest_private::SetTabletModeEnabled::Params::Create(*args_));
+  EXTENSION_FUNCTION_VALIDATE(params);
+  TabletModeClient::Get()->SetTabletModeEnabledForTesting(
+      params->enabled,
+      base::BindOnce(
+          &AutotestPrivateSetTabletModeEnabledFunction::OnSetTabletModeEnabled,
+          this));
+  return RespondLater();
+}
+
+void AutotestPrivateSetTabletModeEnabledFunction::OnSetTabletModeEnabled(
+    bool enabled) {
+  Respond(OneArgument(std::make_unique<base::Value>(enabled)));
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // 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 e33e9dc..61948d2 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -382,14 +382,24 @@
                        scoped_refptr<base::RefCountedMemory> png_data);
 };
 
-class AutotestPrivateGetPrinterListFunction : public UIThreadExtensionFunction {
+class AutotestPrivateGetPrinterListFunction
+    : public UIThreadExtensionFunction,
+      public chromeos::CupsPrintersManager::Observer {
  public:
   DECLARE_EXTENSION_FUNCTION("autotestPrivate.getPrinterList",
                              AUTOTESTPRIVATE_GETPRINTERLIST)
+  AutotestPrivateGetPrinterListFunction();
 
  private:
   ~AutotestPrivateGetPrinterListFunction() override;
   ResponseAction Run() override;
+  void RespondWithTimeoutError();
+  void RespondWithSuccess();
+  // chromeos::CupsPrintersManager::Observer
+  void OnEnterprisePrintersInitialized() override;
+  std::unique_ptr<base::Value> results_;
+  std::unique_ptr<chromeos::CupsPrintersManager> printers_manager_;
+  base::OneShotTimer timeout_timer_;
 };
 
 class AutotestPrivateUpdatePrinterFunction : public UIThreadExtensionFunction {
@@ -596,6 +606,19 @@
   ResponseAction Run() override;
 };
 
+// Enables/Disables tablet mode.
+class AutotestPrivateSetTabletModeEnabledFunction
+    : public UIThreadExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("autotestPrivate.setTabletModeEnabled",
+                             AUTOTESTPRIVATE_SETTABLETMODEENABLED)
+
+ private:
+  void OnSetTabletModeEnabled(bool enabled);
+  ~AutotestPrivateSetTabletModeEnabledFunction() override;
+  ResponseAction Run() override;
+};
+
 template <>
 KeyedService*
 BrowserContextKeyedAPIFactory<AutotestPrivateAPI>::BuildServiceInstanceFor(
diff --git a/chrome/browser/chromeos/extensions/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chrome/browser/chromeos/extensions/quick_unlock_private/quick_unlock_private_api_unittest.cc
index 7b2c088d..715d1c56 100644
--- a/chrome/browser/chromeos/extensions/quick_unlock_private/quick_unlock_private_api_unittest.cc
+++ b/chrome/browser/chromeos/extensions/quick_unlock_private/quick_unlock_private_api_unittest.cc
@@ -135,9 +135,8 @@
     : public ExtensionApiUnittest,
       public ::testing::WithParamInterface<TestType> {
  public:
-  QuickUnlockPrivateUnitTest()
-      : fake_user_manager_(new FakeChromeUserManager()),
-        scoped_user_manager_(base::WrapUnique(fake_user_manager_)) {}
+  QuickUnlockPrivateUnitTest() = default;
+  ~QuickUnlockPrivateUnitTest() override = default;
 
  protected:
   void SetUp() override {
@@ -152,6 +151,10 @@
         std::move(cryptohome_client));
     SystemSaltGetter::Initialize();
 
+    fake_user_manager_ = new FakeChromeUserManager();
+    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
+        base::WrapUnique(fake_user_manager_));
+
     ExtensionApiUnittest::SetUp();
 
     SystemSaltGetter::Get()->SetRawSaltForTesting({1, 2, 3, 4, 5, 6, 7, 8});
@@ -190,10 +193,13 @@
 
     base::RunLoop().RunUntilIdle();
 
-    fake_user_manager_ = nullptr;
-
     ExtensionApiUnittest::TearDown();
+
+    fake_user_manager_ = nullptr;
+    scoped_user_manager_.reset();
+
     SystemSaltGetter::Shutdown();
+    DBusThreadManager::Shutdown();
     cryptohome::HomedirMethods::Shutdown();
   }
 
@@ -501,8 +507,8 @@
     expect_modes_changed_ = false;
   }
 
-  FakeChromeUserManager* fake_user_manager_;
-  user_manager::ScopedUserManager scoped_user_manager_;
+  FakeChromeUserManager* fake_user_manager_ = nullptr;
+  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   QuickUnlockPrivateSetModesFunction::ModesChangedEventHandler
       modes_changed_handler_;
   bool expect_modes_changed_ = false;
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
index 86d3f74..a5e6d62 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.cc
@@ -107,23 +107,9 @@
     multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
     : EasyUnlockService(profile, secure_channel_client),
       lock_screen_last_shown_timestamp_(base::TimeTicks::Now()),
-      deferring_device_load_(false),
       notification_controller_(std::move(notification_controller)),
       device_sync_client_(device_sync_client),
-      multidevice_setup_client_(multidevice_setup_client),
-      shown_pairing_changed_notification_(false),
-      weak_ptr_factory_(this) {
-  // If |device_sync_client_| is not ready yet, wait for it to call back on
-  // OnReady().
-  if (device_sync_client_->is_ready())
-    OnReady();
-
-  device_sync_client_->AddObserver(this);
-
-  OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates());
-
-  multidevice_setup_client_->AddObserver(this);
-}
+      multidevice_setup_client_(multidevice_setup_client) {}
 
 EasyUnlockServiceRegular::~EasyUnlockServiceRegular() = default;
 
@@ -347,6 +333,17 @@
 }
 
 void EasyUnlockServiceRegular::InitializeInternal() {
+  // If |device_sync_client_| is not ready yet, wait for it to call back on
+  // OnReady().
+  if (device_sync_client_->is_ready())
+    OnReady();
+
+  device_sync_client_->AddObserver(this);
+
+  OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates());
+
+  multidevice_setup_client_->AddObserver(this);
+
   proximity_auth::ScreenlockBridge::Get()->AddObserver(this);
 
   pref_manager_.reset(new proximity_auth::ProximityAuthProfilePrefManager(
diff --git a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.h b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.h
index 711f7f25..f03dd78 100644
--- a/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.h
+++ b/chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.h
@@ -145,7 +145,7 @@
   // loading the RemoteDevice until the screen is unlocked. For security,
   // this deferment prevents the lock screen from being changed by a network
   // event.
-  bool deferring_device_load_;
+  bool deferring_device_load_ = false;
 
   // Responsible for showing all the notifications used for EasyUnlock.
   std::unique_ptr<EasyUnlockNotificationController> notification_controller_;
@@ -170,12 +170,12 @@
   // True if the pairing changed notification was shown, so that the next time
   // the Chromebook is unlocked, we can show the subsequent 'pairing applied'
   // notification.
-  bool shown_pairing_changed_notification_;
+  bool shown_pairing_changed_notification_ = false;
 
   // Listens to pref changes.
   PrefChangeRegistrar registrar_;
 
-  base::WeakPtrFactory<EasyUnlockServiceRegular> weak_ptr_factory_;
+  base::WeakPtrFactory<EasyUnlockServiceRegular> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(EasyUnlockServiceRegular);
 };
diff --git a/chrome/browser/chromeos/login/screens/mock_network_screen.cc b/chrome/browser/chromeos/login/screens/mock_network_screen.cc
index 9e06e24..40bc0b9 100644
--- a/chrome/browser/chromeos/login/screens/mock_network_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_network_screen.cc
@@ -10,11 +10,16 @@
 using ::testing::_;
 
 MockNetworkScreen::MockNetworkScreen(BaseScreenDelegate* base_screen_delegate,
-                                     NetworkScreenView* view)
-    : NetworkScreen(base_screen_delegate, view) {}
+                                     NetworkScreenView* view,
+                                     const ScreenExitCallback& exit_callback)
+    : NetworkScreen(base_screen_delegate, view, exit_callback) {}
 
 MockNetworkScreen::~MockNetworkScreen() = default;
 
+void MockNetworkScreen::ExitScreen(NetworkScreen::Result result) {
+  exit_callback()->Run(result);
+}
+
 MockNetworkScreenView::MockNetworkScreenView() {
   EXPECT_CALL(*this, MockBind(_)).Times(AtLeast(1));
 }
diff --git a/chrome/browser/chromeos/login/screens/mock_network_screen.h b/chrome/browser/chromeos/login/screens/mock_network_screen.h
index 061f995c..c35f9fd5 100644
--- a/chrome/browser/chromeos/login/screens/mock_network_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_network_screen.h
@@ -15,12 +15,15 @@
 class MockNetworkScreen : public NetworkScreen {
  public:
   MockNetworkScreen(BaseScreenDelegate* base_screen_delegate,
-                    NetworkScreenView* view);
+                    NetworkScreenView* view,
+                    const ScreenExitCallback& exit_callback);
   ~MockNetworkScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
 
+  void ExitScreen(NetworkScreen::Result result);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockNetworkScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/network_screen.cc b/chrome/browser/chromeos/login/screens/network_screen.cc
index 23e55a10e..5ea06a9 100644
--- a/chrome/browser/chromeos/login/screens/network_screen.cc
+++ b/chrome/browser/chromeos/login/screens/network_screen.cc
@@ -39,9 +39,11 @@
 }
 
 NetworkScreen::NetworkScreen(BaseScreenDelegate* base_screen_delegate,
-                             NetworkScreenView* view)
+                             NetworkScreenView* view,
+                             const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_NETWORK),
       view_(view),
+      exit_callback_(exit_callback),
       network_state_helper_(std::make_unique<login::NetworkStateHelper>()),
       weak_ptr_factory_(this) {
   if (view_)
@@ -135,7 +137,7 @@
   // TODO(nkostylev): Check network connectivity.
   UnsubscribeNetworkNotification();
   connection_timer_.Stop();
-  Finish(ScreenExitCode::NETWORK_CONNECTED);
+  exit_callback_.Run(Result::CONNECTED);
 }
 
 void NetworkScreen::OnConnectionTimeout() {
@@ -201,7 +203,7 @@
 void NetworkScreen::OnBackButtonClicked() {
   if (view_)
     view_->ClearErrors();
-  Finish(ScreenExitCode::NETWORK_BACK);
+  exit_callback_.Run(Result::BACK);
 }
 
 void NetworkScreen::OnContinueButtonClicked() {
@@ -227,7 +229,7 @@
   DCHECK(DemoSetupController::IsOobeDemoSetupFlowInProgress());
   if (view_)
     view_->ClearErrors();
-  Finish(ScreenExitCode::NETWORK_OFFLINE_DEMO_SETUP);
+  exit_callback_.Run(Result::OFFLINE_DEMO_SETUP);
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_screen.h b/chrome/browser/chromeos/login/screens/network_screen.h
index 7fb9db3..4ec7d4c 100644
--- a/chrome/browser/chromeos/login/screens/network_screen.h
+++ b/chrome/browser/chromeos/login/screens/network_screen.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/callback.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -28,8 +29,12 @@
 // Controls network selection screen shown during OOBE.
 class NetworkScreen : public BaseScreen, public NetworkStateHandlerObserver {
  public:
+  enum class Result { CONNECTED, OFFLINE_DEMO_SETUP, BACK };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   NetworkScreen(BaseScreenDelegate* base_screen_delegate,
-                NetworkScreenView* view);
+                NetworkScreenView* view,
+                const ScreenExitCallback& exit_callback);
   ~NetworkScreen() override;
 
   // Returns instance of NetworkScreen.
@@ -39,6 +44,14 @@
   // the |view| it should call view->Unbind().
   void OnViewDestroyed(NetworkScreenView* view);
 
+  void set_exit_callback_for_testing(const ScreenExitCallback& exit_callback) {
+    exit_callback_ = exit_callback;
+  }
+
+ protected:
+  // Give test overrides access to the exit callback.
+  ScreenExitCallback* exit_callback() { return &exit_callback_; }
+
  private:
   friend class NetworkScreenTest;
   friend class NetworkScreenUnitTest;
@@ -118,6 +131,7 @@
   base::OneShotTimer connection_timer_;
 
   NetworkScreenView* view_ = nullptr;
+  ScreenExitCallback exit_callback_;
   std::unique_ptr<login::NetworkStateHelper> network_state_helper_;
 
   base::WeakPtrFactory<NetworkScreen> weak_ptr_factory_;
diff --git a/chrome/browser/chromeos/login/screens/network_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/network_screen_browsertest.cc
index e7809e0..b8b02a2 100644
--- a/chrome/browser/chromeos/login/screens/network_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/network_screen_browsertest.cc
@@ -6,8 +6,10 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "base/command_line.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "chrome/browser/chromeos/login/enrollment/enrollment_screen.h"
 #include "chrome/browser/chromeos/login/helper.h"
@@ -69,6 +71,8 @@
     ASSERT_EQ(WizardController::default_controller()->current_screen(),
               network_screen_);
     network_screen_->base_screen_delegate_ = mock_base_screen_delegate_.get();
+    network_screen_->set_exit_callback_for_testing(base::BindRepeating(
+        &NetworkScreenTest::HandleScreenExit, base::Unretained(this)));
     ASSERT_TRUE(network_screen_->view_ != nullptr);
 
     mock_network_state_helper_ = new login::MockNetworkStateHelper;
@@ -77,12 +81,12 @@
   }
 
   void EmulateContinueButtonExit(NetworkScreen* network_screen) {
-    EXPECT_CALL(*mock_base_screen_delegate_,
-                OnExit(ScreenExitCode::NETWORK_CONNECTED))
-        .Times(1);
     EXPECT_CALL(*network_state_helper(), IsConnected()).WillOnce(Return(true));
     network_screen->OnContinueButtonClicked();
     base::RunLoop().RunUntilIdle();
+
+    ASSERT_TRUE(last_screen_result_.has_value());
+    EXPECT_EQ(NetworkScreen::Result::CONNECTED, last_screen_result_.value());
   }
 
   void SetDefaultNetworkStateHelperExpectations() {
@@ -103,9 +107,15 @@
   NetworkScreen* network_screen() { return network_screen_; }
 
  private:
+  void HandleScreenExit(NetworkScreen::Result result) {
+    EXPECT_FALSE(last_screen_result_.has_value());
+    last_screen_result_ = result;
+  }
+
   std::unique_ptr<MockBaseScreenDelegate> mock_base_screen_delegate_;
   login::MockNetworkStateHelper* mock_network_state_helper_;
   NetworkScreen* network_screen_;
+  base::Optional<NetworkScreen::Result> last_screen_result_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkScreenTest);
 };
diff --git a/chrome/browser/chromeos/login/screens/network_screen_unittest.cc b/chrome/browser/chromeos/login/screens/network_screen_unittest.cc
index ad6bb60..f967f889 100644
--- a/chrome/browser/chromeos/login/screens/network_screen_unittest.cc
+++ b/chrome/browser/chromeos/login/screens/network_screen_unittest.cc
@@ -6,7 +6,9 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "base/command_line.h"
+#include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_mock_time_message_loop_task_runner.h"
 #include "chrome/browser/chromeos/login/mock_network_state_helper.h"
@@ -41,7 +43,9 @@
 
     // Create the NetworkScreen we will use for testing.
     network_screen_ = std::make_unique<NetworkScreen>(
-        &mock_base_screen_delegate_, &mock_view_);
+        &mock_base_screen_delegate_, &mock_view_,
+        base::BindRepeating(&NetworkScreenUnitTest::HandleScreenExit,
+                            base::Unretained(this)));
     network_screen_->set_model_view_channel(&mock_channel_);
     mock_network_state_helper_ = new login::MockNetworkStateHelper();
     network_screen_->SetNetworkStateHelperForTest(mock_network_state_helper_);
@@ -60,8 +64,14 @@
   // Accessory objects needed by NetworkScreen.
   MockBaseScreenDelegate mock_base_screen_delegate_;
   login::MockNetworkStateHelper* mock_network_state_helper_ = nullptr;
+  base::Optional<NetworkScreen::Result> last_screen_result_;
 
  private:
+  void HandleScreenExit(NetworkScreen::Result screen_result) {
+    EXPECT_FALSE(last_screen_result_.has_value());
+    last_screen_result_ = screen_result;
+  }
+
   // Test versions of core browser infrastructure.
   content::TestBrowserThreadBundle threads_;
 
@@ -73,11 +83,6 @@
 };
 
 TEST_F(NetworkScreenUnitTest, ContinuesAutomatically) {
-  // Set expectation that NetworkScreen will finish.
-  EXPECT_CALL(mock_base_screen_delegate_,
-              OnExit(ScreenExitCode::NETWORK_CONNECTED))
-      .Times(1);
-
   // Simulate a network connection.
   EXPECT_CALL(*mock_network_state_helper_, IsConnected())
       .Times(AnyNumber())
@@ -86,14 +91,12 @@
 
   // Check that we continued once
   EXPECT_EQ(1, network_screen_->continue_attempts_);
+
+  ASSERT_TRUE(last_screen_result_.has_value());
+  EXPECT_EQ(NetworkScreen::Result::CONNECTED, last_screen_result_.value());
 }
 
 TEST_F(NetworkScreenUnitTest, ContinuesOnlyOnce) {
-  // Set expectation that NetworkScreen will finish.
-  EXPECT_CALL(mock_base_screen_delegate_,
-              OnExit(ScreenExitCode::NETWORK_CONNECTED))
-      .Times(1);
-
   // Connect to network "net0".
   EXPECT_CALL(*mock_network_state_helper_, GetCurrentNetworkName())
       .Times(AnyNumber())
@@ -108,6 +111,9 @@
   // Check that we have continued exactly once.
   ASSERT_EQ(1, network_screen_->continue_attempts_);
 
+  ASSERT_TRUE(last_screen_result_.has_value());
+  EXPECT_EQ(NetworkScreen::Result::CONNECTED, last_screen_result_.value());
+
   // Stop waiting for another network, net1.
   network_screen_->StopWaitingForConnection(base::ASCIIToUTF16("net1"));
 
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.cc b/chrome/browser/chromeos/login/screens/screen_exit_code.cc
index b1d75c74..cbb4a6cd 100644
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.cc
+++ b/chrome/browser/chromeos/login/screens/screen_exit_code.cc
@@ -80,11 +80,11 @@
       return "ARC_TERMS_OF_SERVICE_BACK";
     case ScreenExitCode::DISCOVER_FINISHED:
       return "DISCOVER_FINISHED";
-    case ScreenExitCode::NETWORK_BACK:
+    case ScreenExitCode::DEPRECATED_NETWORK_BACK:
       return "NETWORK_BACK";
-    case ScreenExitCode::NETWORK_CONNECTED:
+    case ScreenExitCode::DEPRECATED_NETWORK_CONNECTED:
       return "NETWORK_CONNECTED";
-    case ScreenExitCode::NETWORK_OFFLINE_DEMO_SETUP:
+    case ScreenExitCode::DEPRECATED_NETWORK_OFFLINE_DEMO_SETUP:
       return "NETWORK_OFFLINE_DEMO_SETUP";
     case ScreenExitCode::FINGERPRINT_SETUP_FINISHED:
       return "FINGERPRINT_SETUP_FINISHED";
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.h b/chrome/browser/chromeos/login/screens/screen_exit_code.h
index 498ba53..721b74f 100644
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.h
+++ b/chrome/browser/chromeos/login/screens/screen_exit_code.h
@@ -61,9 +61,9 @@
   APP_DOWNLOADING_FINISHED = 39,
   ARC_TERMS_OF_SERVICE_BACK = 40,
   DISCOVER_FINISHED = 41,
-  NETWORK_BACK = 42,
-  NETWORK_CONNECTED = 43,
-  NETWORK_OFFLINE_DEMO_SETUP = 44,
+  DEPRECATED_NETWORK_BACK = 42,
+  DEPRECATED_NETWORK_CONNECTED = 43,
+  DEPRECATED_NETWORK_OFFLINE_DEMO_SETUP = 44,
   FINGERPRINT_SETUP_FINISHED = 45,
   MARKETING_OPT_IN_FINISHED = 46,
   ASSISTANT_OPTIN_FLOW_FINISHED = 47,
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.h b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
index 63a10bd..e18ec7d 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/chromeos/login/existing_user_controller.h"
 #include "chrome/browser/chromeos/login/oobe_configuration.h"
 #include "chrome/browser/chromeos/login/signin_screen_controller.h"
+#include "chrome/browser/chromeos/login/ui/kiosk_app_menu_updater.h"
 #include "chrome/browser/chromeos/login/ui/login_display.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host_common.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
@@ -271,6 +272,9 @@
   // True if we need to play startup sound when audio device becomes available.
   bool need_to_play_startup_sound_ = false;
 
+  // Updates shelf kiosk app list.
+  KioskAppMenuUpdater kiosk_updater_;
+
   base::WeakPtrFactory<LoginDisplayHostWebUI> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginDisplayHostWebUI);
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index 0ca250f0..99defc4 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -52,8 +52,8 @@
 #include "chrome/browser/chromeos/login/users/supervised_user_manager_impl.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_network_configuration_updater.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/session_length_limiter.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
@@ -194,9 +194,9 @@
       ->GetMinimumVersionPolicyHandler();
 }
 
-base::WeakPtr<ExternalPrinters> GetExternalPrinters(
+base::WeakPtr<BulkPrintersCalculator> GetBulkPrintersCalculator(
     const AccountId& account_id) {
-  return ExternalPrintersFactory::Get()->GetForAccountId(account_id);
+  return BulkPrintersCalculatorFactory::Get()->GetForAccountId(account_id);
 }
 
 // Starts bluetooth logging service for accounts ending with |kGoogleDotCom|
@@ -466,7 +466,7 @@
   wallpaper_policy_observer_.reset();
   // Remove the observer before shutting down the printer policy objects.
   printers_policy_observer_.reset();
-  ExternalPrintersFactory::Get()->Shutdown();
+  BulkPrintersCalculatorFactory::Get()->ShutdownProfiles();
   registrar_.RemoveAll();
 }
 
@@ -712,7 +712,7 @@
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataSet(policy);
   else if (policy == policy::key::kNativePrintersBulkConfiguration)
-    GetExternalPrinters(account_id)->ClearData();
+    GetBulkPrintersCalculator(account_id)->ClearData();
   else if (policy != policy::key::kWallpaperImage)
     NOTREACHED();
 }
@@ -724,7 +724,7 @@
   if (policy == policy::key::kUserAvatarImage)
     GetUserImageManager(account_id)->OnExternalDataCleared(policy);
   else if (policy == policy::key::kNativePrintersBulkConfiguration)
-    GetExternalPrinters(account_id)->ClearData();
+    GetBulkPrintersCalculator(account_id)->ClearData();
   else if (policy == policy::key::kWallpaperImage)
     WallpaperControllerClient::Get()->RemovePolicyWallpaper(account_id);
   else
@@ -742,7 +742,7 @@
     GetUserImageManager(account_id)
         ->OnExternalDataFetched(policy, std::move(data));
   } else if (policy == policy::key::kNativePrintersBulkConfiguration) {
-    GetExternalPrinters(account_id)->SetData(std::move(data));
+    GetBulkPrintersCalculator(account_id)->SetData(std::move(data));
   } else if (policy == policy::key::kWallpaperImage) {
     WallpaperControllerClient::Get()->SetPolicyWallpaper(account_id,
                                                          std::move(data));
@@ -1163,7 +1163,7 @@
   // |known_user::RemovePrefs|. See https://crbug.com/778077.
   WallpaperControllerClient::Get()->RemoveUserWallpaper(account_id);
   GetUserImageManager(account_id)->DeleteUserImage();
-  ExternalPrintersFactory::Get()->RemoveForUserId(account_id);
+  BulkPrintersCalculatorFactory::Get()->RemoveForUserId(account_id);
   // TODO(tbarzic): Forward data removal request to ash::HammerDeviceHandler,
   // instead of removing the prefs value here.
   if (GetLocalState()->FindPreference(ash::prefs::kDetachableBaseDevices)) {
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
index e75c886..a7dae9b 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.h
@@ -26,7 +26,6 @@
 #include "chrome/browser/chromeos/policy/device_local_account.h"
 #include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
 #include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "components/account_id/account_id.h"
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index dec2e17..5a8975b 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -370,8 +370,10 @@
     return std::make_unique<WelcomeScreen>(this, this,
                                            oobe_ui->GetWelcomeView());
   } else if (screen == OobeScreen::SCREEN_OOBE_NETWORK) {
-    return std::make_unique<NetworkScreen>(this,
-                                           oobe_ui->GetNetworkScreenView());
+    return std::make_unique<NetworkScreen>(
+        this, oobe_ui->GetNetworkScreenView(),
+        base::BindRepeating(&WizardController::OnNetworkScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_UPDATE) {
     return std::make_unique<UpdateScreen>(
         this, oobe_ui->GetUpdateView(),
@@ -727,6 +729,74 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 // WizardController, ExitHandlers:
+void WizardController::OnNetworkScreenExit(NetworkScreen::Result result) {
+  VLOG(1) << "Network screen exit: " << static_cast<int>(result);
+  OnScreenExit(OobeScreen::SCREEN_OOBE_NETWORK);
+
+  if (result == NetworkScreen::Result::BACK) {
+    if (demo_setup_controller_) {
+      ShowDemoModePreferencesScreen();
+    } else {
+      ShowWelcomeScreen();
+    }
+    return;
+  }
+
+  // Update the demo setup config for demo setup flow.
+  if (demo_setup_controller_) {
+    switch (result) {
+      case NetworkScreen::Result::CONNECTED:
+        demo_setup_controller_->set_demo_config(
+            DemoSession::DemoModeConfig::kOnline);
+        break;
+      case NetworkScreen::Result::OFFLINE_DEMO_SETUP:
+        demo_setup_controller_->set_demo_config(
+            DemoSession::DemoModeConfig::kOffline);
+        break;
+      case NetworkScreen::Result::BACK:
+        NOTREACHED();
+    }
+  }
+
+  if (ShowEulaOrArcTosAfterNetworkScreen())
+    return;
+
+  switch (result) {
+    case NetworkScreen::Result::CONNECTED:
+      InitiateOOBEUpdate();
+      break;
+    case NetworkScreen::Result::OFFLINE_DEMO_SETUP:
+      // TODO(agawronska): Maybe check if device is connected to the network
+      // and attempt system update. It is possible to initiate offline demo
+      // setup on the device that is connected, although it is probably not
+      // common.
+      ShowDemoModeSetupScreen();
+      break;
+    case NetworkScreen::Result::BACK:
+      NOTREACHED();
+  }
+}
+
+bool WizardController::ShowEulaOrArcTosAfterNetworkScreen() {
+  if (!is_official_build_)
+    return false;
+
+  if (!StartupUtils::IsEulaAccepted()) {
+    ShowEulaScreen();
+    return true;
+  }
+  if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
+    ShowArcTermsOfServiceScreen();
+    return true;
+  }
+
+  // This is reachable in case of a reboot during previous OOBE flow after EULA
+  // was accepted and ARC terms of service handled - for example due to a forced
+  // update (which is the next step, with the exception of offline demo mode
+  // setup).
+  return false;
+}
+
 void WizardController::OnUpdateScreenExit(UpdateScreen::Result result) {
   VLOG(1) << "Update screen exit: " << static_cast<int>(result);
   OnScreenExit(OobeScreen::SCREEN_OOBE_UPDATE);
@@ -793,6 +863,10 @@
     return;
   }
 
+  // This populates post-OOBE shelf and UI.
+  session_manager::SessionManager::Get()->SetSessionState(
+      session_manager::SessionState::LOGIN_PRIMARY);
+
   if (KioskAppManager::Get()->IsAutoLaunchEnabled())
     AutoLaunchKioskApp();
   else
@@ -809,58 +883,6 @@
   ShowNetworkScreen();
 }
 
-void WizardController::OnNetworkBack() {
-  if (demo_setup_controller_) {
-    ShowDemoModePreferencesScreen();
-  } else {
-    ShowWelcomeScreen();
-  }
-}
-
-void WizardController::OnNetworkConnected() {
-  if (demo_setup_controller_) {
-    demo_setup_controller_->set_demo_config(
-        DemoSession::DemoModeConfig::kOnline);
-  }
-
-  if (is_official_build_) {
-    if (!StartupUtils::IsEulaAccepted()) {
-      ShowEulaScreen();
-    } else if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
-      ShowArcTermsOfServiceScreen();
-    } else {
-      // Possible cases:
-      // 1. EULA was accepted, forced shutdown/reboot during update.
-      // 2. EULA was accepted, planned reboot after update.
-      // Make sure that device is up to date.
-      InitiateOOBEUpdate();
-    }
-  } else {
-    InitiateOOBEUpdate();
-  }
-}
-
-void WizardController::OnOfflineDemoModeSetup() {
-  DCHECK(demo_setup_controller_);
-  demo_setup_controller_->set_demo_config(
-      DemoSession::DemoModeConfig::kOffline);
-
-  if (is_official_build_) {
-    if (!StartupUtils::IsEulaAccepted()) {
-      ShowEulaScreen();
-    } else if (arc::IsArcTermsOfServiceOobeNegotiationNeeded()) {
-      ShowArcTermsOfServiceScreen();
-    } else {
-      ShowDemoModeSetupScreen();
-    }
-  } else {
-    // TODO(agawronska): Maybe check if device is connected to the network and
-    // attempt system update. It is possible to initiate offline demo setup on
-    // the device that is connected, although it is probably not common.
-    ShowDemoModeSetupScreen();
-  }
-}
-
 void WizardController::OnConnectionFailed() {
   // TODO(dpolukhin): show error message after login screen is displayed.
   ShowLoginScreen(LoginScreenContext());
@@ -1411,15 +1433,6 @@
     case ScreenExitCode::WELCOME_CONTINUED:
       OnWelcomeContinued();
       break;
-    case ScreenExitCode::NETWORK_BACK:
-      OnNetworkBack();
-      break;
-    case ScreenExitCode::NETWORK_CONNECTED:
-      OnNetworkConnected();
-      break;
-    case ScreenExitCode::NETWORK_OFFLINE_DEMO_SETUP:
-      OnOfflineDemoModeSetup();
-      break;
     case ScreenExitCode::CONNECTION_FAILED:
       OnConnectionFailed();
       break;
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index 683d253..137a8dc 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -25,6 +25,7 @@
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
 #include "chrome/browser/chromeos/login/screens/eula_screen.h"
 #include "chrome/browser/chromeos/login/screens/hid_detection_screen.h"
+#include "chrome/browser/chromeos/login/screens/network_screen.h"
 #include "chrome/browser/chromeos/login/screens/reset_screen.h"
 #include "chrome/browser/chromeos/login/screens/update_screen.h"
 #include "chrome/browser/chromeos/login/screens/welcome_screen.h"
@@ -196,6 +197,8 @@
   void OnScreenExit(OobeScreen screen);
 
   // Exit handlers:
+  void OnNetworkScreenExit(NetworkScreen::Result result);
+  bool ShowEulaOrArcTosAfterNetworkScreen();
   void OnUpdateScreenExit(UpdateScreen::Result result);
   void OnUpdateCompleted();
   void OnAutoEnrollmentCheckScreenExit();
@@ -203,9 +206,6 @@
   void OnEnrollmentDone();
   void OnHIDDetectionCompleted();
   void OnWelcomeContinued();
-  void OnNetworkBack();
-  void OnNetworkConnected();
-  void OnOfflineDemoModeSetup();
   void OnConnectionFailed();
   void OnEulaAccepted();
   void OnEulaBack();
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 0a0dcad..fb30bc12 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -603,7 +603,9 @@
     mock_network_screen_view_ = std::make_unique<MockNetworkScreenView>();
     mock_network_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockNetworkScreen>(
-            wizard_controller, mock_network_screen_view_.get()));
+            wizard_controller, mock_network_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnNetworkScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_update_view_ = std::make_unique<MockUpdateView>();
     mock_update_screen_ =
@@ -753,7 +755,12 @@
     EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
     EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
     EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
-    OnExit(ScreenExitCode::NETWORK_CONNECTED);
+    EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
+    OnExit(ScreenExitCode::WELCOME_CONTINUED);
+
+    CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
+    EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
+    mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
     CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
     // Login shelf should still be visible.
@@ -860,7 +867,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -900,7 +907,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -933,7 +940,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -973,7 +980,7 @@
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_update_screen_, StartNetworkCheck()).Times(0);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
@@ -1053,7 +1060,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1182,7 +1189,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1227,7 +1234,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1325,7 +1332,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1423,7 +1430,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1567,7 +1574,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1641,7 +1648,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1736,7 +1743,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1780,7 +1787,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1836,7 +1843,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1892,7 +1899,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -1974,7 +1981,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -2139,7 +2146,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -2187,7 +2194,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_CALL(*mock_eula_screen_, Hide()).Times(1);
@@ -2312,7 +2319,7 @@
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2384,7 +2391,7 @@
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::NETWORK_OFFLINE_DEMO_SETUP);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::OFFLINE_DEMO_SETUP);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2439,7 +2446,7 @@
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2520,7 +2527,7 @@
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_demo_preferences_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::NETWORK_BACK);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::BACK);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_DEMO_PREFERENCES);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2609,7 +2616,7 @@
   EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::NETWORK_CONNECTED);
+  mock_network_screen_->ExitScreen(NetworkScreen::Result::CONNECTED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_EULA);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl.cc b/chrome/browser/chromeos/net/network_portal_detector_impl.cc
index 4c7a06a..d12bcdfe 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl.cc
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl.cc
@@ -13,7 +13,9 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/net/system_network_context_manager.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/shill_profile_client.h"
@@ -24,6 +26,7 @@
 #include "content/public/browser/notification_service.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
 using captive_portal::CaptivePortalDetector;
@@ -203,12 +206,21 @@
     NetworkPortalDetectorImpl::kDelaySinceShillPortalForUMA;
 
 NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
-    network::mojom::URLLoaderFactory* loader_factory)
+    network::mojom::URLLoaderFactory* loader_factory_for_testing)
     : strategy_(PortalDetectorStrategy::CreateById(
           PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN,
           this)),
       weak_factory_(this) {
   NET_LOG(EVENT) << "NetworkPortalDetectorImpl::NetworkPortalDetectorImpl()";
+  network::mojom::URLLoaderFactory* loader_factory;
+  if (loader_factory_for_testing) {
+    loader_factory = loader_factory_for_testing;
+  } else {
+    shared_url_loader_factory_ =
+        g_browser_process->system_network_context_manager()
+            ->GetSharedURLLoaderFactory();
+    loader_factory = shared_url_loader_factory_.get();
+  }
   captive_portal_detector_.reset(new CaptivePortalDetector(loader_factory));
 
   portal_test_url_ = GURL(CaptivePortalDetector::kDefaultURL);
diff --git a/chrome/browser/chromeos/net/network_portal_detector_impl.h b/chrome/browser/chromeos/net/network_portal_detector_impl.h
index 18a3ebc4..47111d6 100644
--- a/chrome/browser/chromeos/net/network_portal_detector_impl.h
+++ b/chrome/browser/chromeos/net/network_portal_detector_impl.h
@@ -13,6 +13,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/sequence_checker.h"
@@ -34,10 +35,11 @@
 }
 
 namespace network {
+class SharedURLLoaderFactory;
 namespace mojom {
 class URLLoaderFactory;
 }
-}
+}  // namespace network
 
 namespace chromeos {
 
@@ -57,7 +59,7 @@
       base::TimeDelta::FromSeconds(60);
 
   explicit NetworkPortalDetectorImpl(
-      network::mojom::URLLoaderFactory* loader_factory);
+      network::mojom::URLLoaderFactory* loader_factory_for_testing = nullptr);
   ~NetworkPortalDetectorImpl() override;
 
   // Set the URL to be tested for portal state.
@@ -225,6 +227,9 @@
   base::CancelableClosure attempt_task_;
   base::CancelableClosure attempt_timeout_;
 
+  // Reference to a SharedURLLoaderFactory used to detect portals.
+  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
+
   // URL that returns a 204 response code when connected to the Internet. Used
   // by tests.
   GURL portal_test_url_;
diff --git a/chrome/browser/chromeos/policy/device_native_printers_handler.cc b/chrome/browser/chromeos/policy/device_native_printers_handler.cc
index 1f614bd7..423e1c80 100644
--- a/chrome/browser/chromeos/policy/device_native_printers_handler.cc
+++ b/chrome/browser/chromeos/policy/device_native_printers_handler.cc
@@ -7,16 +7,15 @@
 #include <utility>
 
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "components/policy/policy_constants.h"
 
 namespace policy {
 
 namespace {
 
-base::WeakPtr<chromeos::ExternalPrinters> GetExternalPrinters() {
-  return chromeos::DeviceExternalPrintersFactory::Get()->GetForDevice();
+base::WeakPtr<chromeos::BulkPrintersCalculator> GetBulkPrintersCalculator() {
+  return chromeos::BulkPrintersCalculatorFactory::Get()->GetForDevice();
 }
 
 }  // namespace
@@ -33,25 +32,25 @@
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataSet(
     const std::string& policy) {
-  GetExternalPrinters()->ClearData();
+  GetBulkPrintersCalculator()->ClearData();
 }
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataCleared(
     const std::string& policy) {
-  GetExternalPrinters()->ClearData();
+  GetBulkPrintersCalculator()->ClearData();
 }
 
 void DeviceNativePrintersHandler::OnDeviceExternalDataFetched(
     const std::string& policy,
     std::unique_ptr<std::string> data,
     const base::FilePath& file_path) {
-  GetExternalPrinters()->SetData(std::move(data));
+  GetBulkPrintersCalculator()->SetData(std::move(data));
 }
 
 void DeviceNativePrintersHandler::Shutdown() {
   if (device_native_printers_observer_)
     device_native_printers_observer_.reset();
-  chromeos::DeviceExternalPrintersFactory::Get()->Shutdown();
+  chromeos::BulkPrintersCalculatorFactory::Get()->Shutdown();
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc b/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
index 6fb0a262..e9d1a24 100644
--- a/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
+++ b/chrome/browser/chromeos/policy/device_native_printers_handler_unittest.cc
@@ -8,8 +8,8 @@
 #include <string>
 
 #include "base/test/scoped_task_environment.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "chromeos/settings/cros_settings_names.h"
@@ -73,8 +73,9 @@
     device_native_printers_handler_ =
         std::make_unique<DeviceNativePrintersHandler>(&policy_service_);
     external_printers_ =
-        chromeos::DeviceExternalPrintersFactory::Get()->GetForDevice();
-    external_printers_->SetAccessMode(chromeos::ExternalPrinters::ALL_ACCESS);
+        chromeos::BulkPrintersCalculatorFactory::Get()->GetForDevice();
+    external_printers_->SetAccessMode(
+        chromeos::BulkPrintersCalculator::ALL_ACCESS);
   }
 
   void TearDown() override { device_native_printers_handler_->Shutdown(); }
@@ -83,7 +84,7 @@
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   MockPolicyService policy_service_;
   std::unique_ptr<DeviceNativePrintersHandler> device_native_printers_handler_;
-  base::WeakPtr<chromeos::ExternalPrinters> external_printers_;
+  base::WeakPtr<chromeos::BulkPrintersCalculator> external_printers_;
 };
 
 TEST_F(DeviceNativePrintersHandlerTest, OnDataFetched) {
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator.cc
new file mode 100644
index 0000000..95b7b26
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator.cc
@@ -0,0 +1,382 @@
+// 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/chromeos/printing/bulk_printers_calculator.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/values.h"
+#include "chrome/common/chrome_features.h"
+#include "chromeos/printing/printer_translator.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr int kMaxRecords = 20000;
+
+// Represents a task scheduled to process in the Restrictions class.
+struct TaskDataInternal {
+  const unsigned task_id;  // unique ID in increasing order
+  std::unordered_map<std::string, Printer> printers;  // resultant list (output)
+  explicit TaskDataInternal(unsigned id) : task_id(id) {}
+};
+
+using PrinterCache = std::vector<std::unique_ptr<Printer>>;
+using TaskData = std::unique_ptr<TaskDataInternal>;
+
+// Parses |data|, a JSON blob, into a vector of Printers.  If |data| cannot be
+// parsed, returns nullptr.  This is run off the UI thread as it could be very
+// slow.
+std::unique_ptr<PrinterCache> ParsePrinters(std::unique_ptr<std::string> data) {
+  if (!data) {
+    LOG(WARNING) << "Received null data";
+    return nullptr;
+  }
+  int error_code = 0;
+  int error_line = 0;
+
+  // This could be really slow.
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  std::unique_ptr<base::Value> json_blob =
+      base::JSONReader::ReadAndReturnErrorDeprecated(
+          *data, base::JSONParserOptions::JSON_PARSE_RFC, &error_code,
+          nullptr /* error_msg_out */, &error_line);
+  // It's not valid JSON.  Give up.
+  if (!json_blob || !json_blob->is_list()) {
+    LOG(WARNING) << "Failed to parse printers policy (" << error_code
+                 << ") on line " << error_line;
+    return nullptr;
+  }
+
+  const base::Value::ListStorage& printer_list = json_blob->GetList();
+  if (printer_list.size() > kMaxRecords) {
+    LOG(WARNING) << "Too many records in printers policy: "
+                 << printer_list.size();
+    return nullptr;
+  }
+
+  auto parsed_printers = std::make_unique<PrinterCache>();
+  parsed_printers->reserve(printer_list.size());
+  for (const base::Value& val : printer_list) {
+    // TODO(skau): Convert to the new Value APIs.
+    const base::DictionaryValue* printer_dict;
+    if (!val.GetAsDictionary(&printer_dict)) {
+      LOG(WARNING) << "Entry in printers policy skipped.  Not a dictionary.";
+      continue;
+    }
+
+    auto printer = RecommendedPrinterToPrinter(*printer_dict);
+    if (!printer) {
+      LOG(WARNING) << "Failed to parse printer configuration.  Skipped.";
+      continue;
+    }
+    parsed_printers->push_back(std::move(printer));
+  }
+
+  return parsed_printers;
+}
+
+// Computes the effective printer list using the access mode and
+// blacklist/whitelist.  Methods are required to be sequenced.  This object is
+// the owner of all the policy data. Methods updating the list of available
+// printers take TaskData (see above) as |task_data| parameter and returned it.
+class Restrictions {
+ public:
+  Restrictions() : printers_cache_(nullptr) {
+    DETACH_FROM_SEQUENCE(sequence_checker_);
+  }
+  ~Restrictions() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
+
+  // Sets the printer cache using the policy blob |data|.
+  TaskData SetData(TaskData task_data, std::unique_ptr<std::string> data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    base::ScopedBlockingCall scoped_blocking_call(
+        FROM_HERE, base::BlockingType::MAY_BLOCK);
+    printers_cache_ = ParsePrinters(std::move(data));
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Clear the printer cache.
+  void ClearData() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    printers_cache_.reset();
+  }
+
+  // Sets the access mode to |mode|.
+  TaskData UpdateAccessMode(TaskData task_data,
+                            BulkPrintersCalculator::AccessMode mode) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    mode_ = mode;
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Sets the blacklist to |blacklist|.
+  TaskData UpdateBlacklist(TaskData task_data,
+                           const std::vector<std::string>& blacklist) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    has_blacklist_ = true;
+    blacklist_ = std::set<std::string>(blacklist.begin(), blacklist.end());
+    return ComputePrinters(std::move(task_data));
+  }
+
+  // Sets the whitelist to |whitelist|.
+  TaskData UpdateWhitelist(TaskData task_data,
+                           const std::vector<std::string>& whitelist) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    has_whitelist_ = true;
+    whitelist_ = std::set<std::string>(whitelist.begin(), whitelist.end());
+    return ComputePrinters(std::move(task_data));
+  }
+
+ private:
+  // Returns true if we have enough data to compute the effective printer list.
+  bool IsReady() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!printers_cache_) {
+      return false;
+    }
+    switch (mode_) {
+      case BulkPrintersCalculator::AccessMode::ALL_ACCESS:
+        return true;
+      case BulkPrintersCalculator::AccessMode::BLACKLIST_ONLY:
+        return has_blacklist_;
+      case BulkPrintersCalculator::AccessMode::WHITELIST_ONLY:
+        return has_whitelist_;
+      case BulkPrintersCalculator::AccessMode::UNSET:
+        return false;
+    }
+    NOTREACHED();
+    return false;
+  }
+
+  // Calculates resultant list of available printers.
+  TaskData ComputePrinters(TaskData task_data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (!IsReady()) {
+      return task_data;
+    }
+
+    switch (mode_) {
+      case BulkPrintersCalculator::UNSET:
+        NOTREACHED();
+        break;
+      case BulkPrintersCalculator::WHITELIST_ONLY:
+        for (const auto& printer : *printers_cache_) {
+          if (base::ContainsKey(whitelist_, printer->id())) {
+            task_data->printers.insert({printer->id(), *printer});
+          }
+        }
+        break;
+      case BulkPrintersCalculator::BLACKLIST_ONLY:
+        for (const auto& printer : *printers_cache_) {
+          if (!base::ContainsKey(blacklist_, printer->id())) {
+            task_data->printers.insert({printer->id(), *printer});
+          }
+        }
+        break;
+      case BulkPrintersCalculator::ALL_ACCESS:
+        for (const auto& printer : *printers_cache_) {
+          task_data->printers.insert({printer->id(), *printer});
+        }
+        break;
+    }
+
+    return task_data;
+  }
+
+  // Cache of the parsed printer configuration file.
+  std::unique_ptr<PrinterCache> printers_cache_;
+  // The type of restriction which is enforced.
+  BulkPrintersCalculator::AccessMode mode_ = BulkPrintersCalculator::UNSET;
+  // Blacklist: the list of ids which should not appear in the final list.
+  bool has_blacklist_ = false;
+  std::set<std::string> blacklist_;
+  // Whitelist: the list of the only ids which should appear in the final list.
+  bool has_whitelist_ = false;
+  std::set<std::string> whitelist_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(Restrictions);
+};
+
+class BulkPrintersCalculatorImpl : public BulkPrintersCalculator {
+ public:
+  BulkPrintersCalculatorImpl()
+      : restrictions_(std::make_unique<Restrictions>()),
+        restrictions_runner_(base::CreateSequencedTaskRunnerWithTraits(
+            {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
+             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
+        weak_ptr_factory_(this) {}
+  ~BulkPrintersCalculatorImpl() override {
+    bool success =
+        restrictions_runner_->DeleteSoon(FROM_HERE, std::move(restrictions_));
+    if (!success) {
+      LOG(WARNING) << "Unable to schedule deletion of policy object.";
+    }
+  }
+
+  void AddObserver(Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.AddObserver(observer);
+  }
+
+  void RemoveObserver(Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.RemoveObserver(observer);
+  }
+
+  void ClearData() override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      return;
+    }
+    data_is_set_ = false;
+    last_processed_task_ = ++last_received_task_;
+    printers_.clear();
+    // Forward data to Restrictions to clear "Data".
+    restrictions_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&Restrictions::ClearData,
+                                  base::Unretained(restrictions_.get())));
+    // Notify observers.
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(this);
+    }
+  }
+
+  void SetData(std::unique_ptr<std::string> data) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      return;
+    }
+    data_is_set_ = true;
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::SetData,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), std::move(data)),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetAccessMode(AccessMode mode) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateAccessMode,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), mode),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetBlacklist(const std::vector<std::string>& blacklist) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateBlacklist,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), blacklist),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void SetWhitelist(const std::vector<std::string>& whitelist) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    TaskData task_data =
+        std::make_unique<TaskDataInternal>(++last_received_task_);
+    base::PostTaskAndReplyWithResult(
+        restrictions_runner_.get(), FROM_HERE,
+        base::BindOnce(&Restrictions::UpdateWhitelist,
+                       base::Unretained(restrictions_.get()),
+                       std::move(task_data), whitelist),
+        base::BindOnce(&BulkPrintersCalculatorImpl::OnComputationComplete,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  bool IsDataPolicySet() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return data_is_set_;
+  }
+
+  bool IsComplete() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return (last_processed_task_ == last_received_task_);
+  }
+
+  const std::unordered_map<std::string, Printer>& GetPrinters() const override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return printers_;
+  }
+
+ private:
+  // Called on computation completion. |task_data| corresponds to finalized
+  // task.
+  void OnComputationComplete(TaskData task_data) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!task_data || task_data->task_id <= last_processed_task_) {
+      // The task is outdated (ClearData() was called in the meantime).
+      return;
+    }
+    last_processed_task_ = task_data->task_id;
+    if (last_processed_task_ < last_received_task_ && printers_.empty() &&
+        task_data->printers.empty()) {
+      // No changes in the object's state.
+      return;
+    }
+    printers_.swap(task_data->printers);
+    task_data.reset();
+    // Notifies observers about changes.
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(this);
+    }
+  }
+
+  // Holds the blacklist and whitelist.  Computes the effective printer list.
+  std::unique_ptr<Restrictions> restrictions_;
+  // Off UI sequence for computing the printer view.
+  scoped_refptr<base::SequencedTaskRunner> restrictions_runner_;
+
+  // True if printers_ is based on a current policy.
+  bool data_is_set_ = false;
+  // Id of the last scheduled task.
+  unsigned last_received_task_ = 0;
+  // Id of the last completed task.
+  unsigned last_processed_task_ = 0;
+  // The computed set of printers.
+  std::unordered_map<std::string, Printer> printers_;
+
+  base::ObserverList<BulkPrintersCalculator::Observer>::Unchecked observers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(BulkPrintersCalculatorImpl);
+  base::WeakPtrFactory<BulkPrintersCalculatorImpl> weak_ptr_factory_;
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<BulkPrintersCalculator> BulkPrintersCalculator::Create() {
+  return std::make_unique<BulkPrintersCalculatorImpl>();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator.h b/chrome/browser/chromeos/printing/bulk_printers_calculator.h
new file mode 100644
index 0000000..3bf1bc9
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator.h
@@ -0,0 +1,88 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/printing/printer_configuration.h"
+
+namespace chromeos {
+
+// Calculates a list of available printers from four policies: Data (json with
+// all printers), AccessMode (see below), Whitelist and Blacklist (lists with
+// ids). All methods must be called from the same sequence and all observers'
+// notifications will be called from this sequence. Resultant list of available
+// printers are calculated asynchronously on a dedicated internal sequence.
+class BulkPrintersCalculator
+    : public base::SupportsWeakPtr<BulkPrintersCalculator> {
+ public:
+  // Algorithm used to calculate a list of available printers from the content
+  // of the "Data" policy.
+  enum AccessMode {
+    UNSET = -1,
+    // Printers in the blacklist are disallowed.  Others are allowed.
+    BLACKLIST_ONLY = 0,
+    // Only printers in the whitelist are allowed.
+    WHITELIST_ONLY = 1,
+    // All printers in the "Data" policy are allowed.
+    ALL_ACCESS = 2
+  };
+
+  class Observer {
+   public:
+    // Observer is notified by this call when the state of the object changes.
+    // See the section "Methods returning the state of the object" below to
+    // learn about parameters defining the state of the object. |sender| is
+    // a pointer to the object calling the notification.
+    virtual void OnPrintersChanged(const BulkPrintersCalculator* sender) = 0;
+  };
+
+  static std::unique_ptr<BulkPrintersCalculator> Create();
+  virtual ~BulkPrintersCalculator() = default;
+
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+  // ========================= Methods setting values of the four policies
+
+  // Sets the "Data" policy. |data| is a list of all printers in JSON format.
+  virtual void SetData(std::unique_ptr<std::string> data) = 0;
+  // Clears the "Data" policy.
+  virtual void ClearData() = 0;
+
+  // Sets the "AccessMode" policy. See description of the AccessMode enum.
+  virtual void SetAccessMode(AccessMode mode) = 0;
+  // Sets the "Blacklist" policy. |blacklist| is a list of printers ids.
+  virtual void SetBlacklist(const std::vector<std::string>& blacklist) = 0;
+  // Sets the "Whitelist" policy. |whitelist| is a list of printers ids.
+  virtual void SetWhitelist(const std::vector<std::string>& whitelist) = 0;
+
+  // ========================= Methods returning the state of the object
+  // Methods returning the three parameters defining the state of the object.
+
+  // Returns true if the "Data" policy has been set with SetData(...) method
+  // (may be not processed yet). Returns false if the "Data" policy has been
+  // cleared with ClearData() method or SetData(...) has been never called.
+  virtual bool IsDataPolicySet() const = 0;
+  // Returns false if current policies were not processed yet. Returns true
+  // if there is no on-going calculations and the method below returns the
+  // list of available printers that is up-to-date with current policies.
+  virtual bool IsComplete() const = 0;
+  // Returns a reference to a resultant list of available printers. Keys are
+  // printers ids. If the list of available printers cannot be calculated
+  // (because of some error or missing policy), an empty map is returned.
+  virtual const std::unordered_map<std::string, Printer>& GetPrinters()
+      const = 0;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_H_
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc
new file mode 100644
index 0000000..110b4db
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.cc
@@ -0,0 +1,80 @@
+// 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/chromeos/printing/bulk_printers_calculator_factory.h"
+
+#include <memory>
+
+#include "base/lazy_instance.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/user_manager/user.h"
+
+namespace chromeos {
+
+namespace {
+
+base::LazyInstance<BulkPrintersCalculatorFactory>::DestructorAtExit
+    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
+
+}  // namespace
+
+// static
+BulkPrintersCalculatorFactory* BulkPrintersCalculatorFactory::Get() {
+  return g_printers_factory.Pointer();
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForAccountId(const AccountId& account_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto found = printers_by_user_.find(account_id);
+  if (found != printers_by_user_.end()) {
+    return found->second->AsWeakPtr();
+  }
+
+  printers_by_user_[account_id] = BulkPrintersCalculator::Create();
+  return printers_by_user_[account_id]->AsWeakPtr();
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForProfile(Profile* profile) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const user_manager::User* user =
+      ProfileHelper::Get()->GetUserByProfile(profile);
+  if (!user)
+    return nullptr;
+
+  return GetForAccountId(user->GetAccountId());
+}
+
+void BulkPrintersCalculatorFactory::RemoveForUserId(
+    const AccountId& account_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.erase(account_id);
+}
+
+base::WeakPtr<BulkPrintersCalculator>
+BulkPrintersCalculatorFactory::GetForDevice() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!device_printers_)
+    device_printers_ = BulkPrintersCalculator::Create();
+  return device_printers_->AsWeakPtr();
+}
+
+void BulkPrintersCalculatorFactory::ShutdownProfiles() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.clear();
+}
+
+void BulkPrintersCalculatorFactory::Shutdown() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  printers_by_user_.clear();
+  device_printers_.reset();
+}
+
+BulkPrintersCalculatorFactory::BulkPrintersCalculatorFactory() = default;
+BulkPrintersCalculatorFactory::~BulkPrintersCalculatorFactory() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h
new file mode 100644
index 0000000..2755ac7c
--- /dev/null
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h
@@ -0,0 +1,68 @@
+// 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_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
+
+#include <map>
+#include <memory>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "components/account_id/account_id.h"
+
+class Profile;
+
+namespace chromeos {
+
+// Dispenses BulkPrintersCalculator objects based on account id.  Access to this
+// object should be sequenced.
+class BulkPrintersCalculatorFactory {
+ public:
+  static BulkPrintersCalculatorFactory* Get();
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for
+  // |account_id|. If an BulkPrintersCalculator does not exist, one will be
+  // created for |account_id|. The returned object remains valid until
+  // RemoveForUserId or Shutdown is called.
+  base::WeakPtr<BulkPrintersCalculator> GetForAccountId(
+      const AccountId& account_id);
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for |profile|
+  // which could be null if |profile| does not map to a valid AccountId. The
+  // returned object remains valid until RemoveForUserId or Shutdown is called.
+  base::WeakPtr<BulkPrintersCalculator> GetForProfile(Profile* profile);
+
+  // Returns a WeakPtr to the BulkPrintersCalculator registered for the device.
+  base::WeakPtr<BulkPrintersCalculator> GetForDevice();
+
+  // Deletes the BulkPrintersCalculator registered for |account_id|.
+  void RemoveForUserId(const AccountId& account_id);
+
+  // Tear down all BulkPrintersCalculator created for users/profiles.
+  void ShutdownProfiles();
+
+  // Tear down all BulkPrintersCalculator.
+  void Shutdown();
+
+ private:
+  friend struct base::LazyInstanceTraitsBase<BulkPrintersCalculatorFactory>;
+
+  BulkPrintersCalculatorFactory();
+  ~BulkPrintersCalculatorFactory();
+
+  std::map<AccountId, std::unique_ptr<BulkPrintersCalculator>>
+      printers_by_user_;
+  std::unique_ptr<BulkPrintersCalculator> device_printers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(BulkPrintersCalculatorFactory);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_BULK_PRINTERS_CALCULATOR_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_unittest.cc b/chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
similarity index 67%
rename from chrome/browser/chromeos/printing/external_printers_unittest.cc
rename to chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
index 35bea3e..7b3e7904 100644
--- a/chrome/browser/chromeos/printing/external_printers_unittest.cc
+++ b/chrome/browser/chromeos/printing/bulk_printers_calculator_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/chromeos/printing/external_printers.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
 
 #include <string>
 #include <vector>
@@ -77,13 +77,10 @@
 ])json";
 
 // Observer that counts the number of times it has been called.
-class TestObserver : public ExternalPrinters::Observer {
+class TestObserver : public BulkPrintersCalculator::Observer {
  public:
-  void OnPrintersChanged(
-      bool valid,
-      const std::map<const std::string, const Printer>& /* printers */)
-      override {
-    last_valid = valid;
+  void OnPrintersChanged(const BulkPrintersCalculator* sender) override {
+    last_valid = sender->IsComplete();
     called++;
   }
 
@@ -93,20 +90,20 @@
   bool last_valid = false;
 };
 
-class ExternalPrintersTest : public testing::Test {
+class BulkPrintersCalculatorTest : public testing::Test {
  public:
-  ExternalPrintersTest() : scoped_task_environment_() {
+  BulkPrintersCalculatorTest() : scoped_task_environment_() {
     scoped_feature_list_.InitAndEnableFeature(
         base::Feature(features::kBulkPrinters));
-    external_printers_ = ExternalPrinters::Create();
+    external_printers_ = BulkPrintersCalculator::Create();
   }
-  ~ExternalPrintersTest() override {
+  ~BulkPrintersCalculatorTest() override {
     // Delete the printer before the task environment.
     external_printers_.reset();
   }
 
  protected:
-  std::unique_ptr<ExternalPrinters> external_printers_;
+  std::unique_ptr<BulkPrintersCalculator> external_printers_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 
  private:
@@ -114,16 +111,17 @@
 };
 
 // Verify that we're initiall unset and empty.
-TEST_F(ExternalPrintersTest, InitialConditions) {
-  EXPECT_FALSE(external_printers_->IsPolicySet());
+TEST_F(BulkPrintersCalculatorTest, InitialConditions) {
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 }
 
 // Verify that the object can be destroyed while parsing is in progress.
-TEST_F(ExternalPrintersTest, DestructionIsSafe) {
+TEST_F(BulkPrintersCalculatorTest, DestructionIsSafe) {
   {
-    std::unique_ptr<ExternalPrinters> printers = ExternalPrinters::Create();
-    printers->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+    std::unique_ptr<BulkPrintersCalculator> printers =
+        BulkPrintersCalculator::Create();
+    printers->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
     printers->SetBlacklist({"Third"});
     printers->SetData(std::make_unique<std::string>(kBulkPolicyContentsJson));
     // Data is valid.  Computation is proceeding.
@@ -133,51 +131,28 @@
   scoped_task_environment_.RunUntilIdle();
 }
 
-// Verifies that all IsPolicySet returns false until all necessary data is set.
-TEST_F(ExternalPrintersTest, PolicyUnsetWithMissingData) {
+// Verifies that IsDataPolicySet returns false until data is set.
+TEST_F(BulkPrintersCalculatorTest, PolicyUnsetWithMissingData) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   external_printers_->SetData(std::move(data));
-
-  // Waiting for AccessMode.
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
-
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
-
-  external_printers_->SetAccessMode(
-      ExternalPrinters::AccessMode::WHITELIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());  // Waiting for Whitelist.
-
-  std::vector<std::string> whitelist = {"First", "Third"};
-  external_printers_->SetWhitelist(whitelist);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());  // Everything is set.
-
-  external_printers_->SetAccessMode(
-      ExternalPrinters::AccessMode::BLACKLIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());  // Blacklist needed now.
-
-  std::vector<std::string> blacklist = {"Second"};
-  external_printers_->SetBlacklist(blacklist);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(
-      external_printers_->IsPolicySet());  // Blacklist was set.  Ready again.
+  EXPECT_TRUE(external_printers_->IsComplete());
 }
 
 // Verify printer list after all attributes have been set.
-TEST_F(ExternalPrintersTest, AllPoliciesResultInPrinters) {
+TEST_F(BulkPrintersCalculatorTest, AllPoliciesResultInPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
+  external_printers_->SetAccessMode(
+      BulkPrintersCalculator::AccessMode::ALL_ACCESS);
   external_printers_->SetData(std::move(data));
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
 
   scoped_task_environment_.RunUntilIdle();
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
   EXPECT_EQ(kNumPrinters, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
   EXPECT_EQ("Color Laser", printers.at("Second").display_name());
@@ -185,34 +160,34 @@
 }
 
 // The external policy was cleared, results should be invalidated.
-TEST_F(ExternalPrintersTest, PolicyClearedNowUnset) {
+TEST_F(BulkPrintersCalculatorTest, PolicyClearedNowUnset) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
-  external_printers_->SetAccessMode(ExternalPrinters::AccessMode::ALL_ACCESS);
+  external_printers_->SetAccessMode(
+      BulkPrintersCalculator::AccessMode::ALL_ACCESS);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
 
   scoped_task_environment_.RunUntilIdle();
-  ASSERT_TRUE(external_printers_->IsPolicySet());
+  ASSERT_TRUE(external_printers_->IsDataPolicySet());
 
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
+  EXPECT_FALSE(external_printers_->IsDataPolicySet());
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 }
 
 // Verify that the blacklist policy is applied correctly.  Printers in the
 // blacklist policy should not be available.  Printers not in the blackslist
 // should be available.
-TEST_F(ExternalPrintersTest, BlacklistPolicySet) {
+TEST_F(BulkPrintersCalculatorTest, BlacklistPolicySet) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetBlacklist({"Second", "Third"});
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
 
   scoped_task_environment_.RunUntilIdle();
   const auto& printers = external_printers_->GetPrinters();
@@ -222,79 +197,76 @@
 
 // Verify that the whitelist policy is correctly applied.  Only printers
 // available in the whitelist are available.
-TEST_F(ExternalPrintersTest, WhitelistPolicySet) {
+TEST_F(BulkPrintersCalculatorTest, WhitelistPolicySet) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetWhitelist({"First"});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(1U, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
 }
 
 // Verify that an empty blacklist results in no printer limits.
-TEST_F(ExternalPrintersTest, EmptyBlacklistAllPrinters) {
+TEST_F(BulkPrintersCalculatorTest, EmptyBlacklistAllPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetBlacklist({});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(kNumPrinters, printers.size());
 }
 
 // Verify that an empty whitelist results in no printers.
-TEST_F(ExternalPrintersTest, EmptyWhitelistNoPrinters) {
+TEST_F(BulkPrintersCalculatorTest, EmptyWhitelistNoPrinters) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_FALSE(external_printers_->IsPolicySet());
   external_printers_->SetWhitelist({});
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(0U, printers.size());
 }
 
 // Verify that switching from whitelist to blacklist behaves correctly.
-TEST_F(ExternalPrintersTest, BlacklistToWhitelistSwap) {
+TEST_F(BulkPrintersCalculatorTest, BlacklistToWhitelistSwap) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
   external_printers_->SetWhitelist({"First"});
   external_printers_->SetBlacklist({"First"});
 
   // This should result in 2 printers.  But we're switching the mode anyway.
 
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
+  EXPECT_TRUE(external_printers_->IsComplete());
   const auto& printers = external_printers_->GetPrinters();
   EXPECT_EQ(1U, printers.size());
   EXPECT_EQ("LexaPrint", printers.at("First").display_name());
 }
 
 // Verify that updated configurations are handled properly.
-TEST_F(ExternalPrintersTest, MultipleUpdates) {
+TEST_F(BulkPrintersCalculatorTest, MultipleUpdates) {
   auto data = std::make_unique<std::string>(kBulkPolicyContentsJson);
   external_printers_->ClearData();
   external_printers_->SetData(std::move(data));
-  external_printers_->SetAccessMode(ExternalPrinters::ALL_ACCESS);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::ALL_ACCESS);
   // There will be 3 printers here.  But we don't want to wait for compuation to
   // complete to verify the final value gets used.
 
@@ -307,41 +279,41 @@
 }
 
 // Verifies that the observer is called at the expected times.
-TEST_F(ExternalPrintersTest, ObserverTest) {
+TEST_F(BulkPrintersCalculatorTest, ObserverTest) {
   TestObserver obs;
   external_printers_->AddObserver(&obs);
 
-  external_printers_->SetAccessMode(ExternalPrinters::ALL_ACCESS);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::ALL_ACCESS);
   external_printers_->SetWhitelist(std::vector<std::string>());
   external_printers_->SetBlacklist(std::vector<std::string>());
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(0, obs.called);
+  EXPECT_EQ(1, obs.called);
 
   external_printers_->SetData(
       std::make_unique<std::string>(kBulkPolicyContentsJson));
 
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_TRUE(external_printers_->IsPolicySet());
-  EXPECT_EQ(1, obs.called);
+  EXPECT_TRUE(external_printers_->IsDataPolicySet());
+  EXPECT_EQ(2, obs.called);
   EXPECT_TRUE(obs.last_valid);  // ready now
   // Printer list is correct after notification.
   EXPECT_EQ(kNumPrinters, external_printers_->GetPrinters().size());
 
-  external_printers_->SetAccessMode(ExternalPrinters::WHITELIST_ONLY);
-  scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(2, obs.called);  // effective list changed.  Notified.
-  EXPECT_TRUE(obs.last_valid);
-
-  external_printers_->SetAccessMode(ExternalPrinters::BLACKLIST_ONLY);
+  external_printers_->SetAccessMode(BulkPrintersCalculator::WHITELIST_ONLY);
   scoped_task_environment_.RunUntilIdle();
   EXPECT_EQ(3, obs.called);  // effective list changed.  Notified.
   EXPECT_TRUE(obs.last_valid);
 
+  external_printers_->SetAccessMode(BulkPrintersCalculator::BLACKLIST_ONLY);
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(4, obs.called);  // effective list changed.  Notified.
+  EXPECT_TRUE(obs.last_valid);
+
   external_printers_->ClearData();
   scoped_task_environment_.RunUntilIdle();
-  EXPECT_EQ(4, obs.called);  // Called for transition to invalid policy.
-  EXPECT_FALSE(obs.last_valid);
+  EXPECT_EQ(5, obs.called);  // Called for transition to invalid policy.
+  EXPECT_TRUE(obs.last_valid);
   EXPECT_TRUE(external_printers_->GetPrinters().empty());
 
   // cleanup
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder.cc b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
new file mode 100644
index 0000000..1f422df
--- /dev/null
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder.cc
@@ -0,0 +1,195 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/printing/calculators_policies_binder.h"
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/policy/profile_policy_connector_factory.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/settings/cros_settings_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+
+namespace {
+
+// It stores the number of bindings (instances of this class) connected to each
+// BulkPrintersCalculator object. It allows us to make sure, that every
+// BulkPrintersCalculator object is not binded more that once.
+std::map<BulkPrintersCalculator*, unsigned>& BindingsCount() {
+  static base::NoDestructor<std::map<BulkPrintersCalculator*, unsigned>>
+      bindings_count;
+  return *bindings_count;
+}
+
+BulkPrintersCalculator::AccessMode ConvertToAccessMode(int mode_val) {
+  if (mode_val >= BulkPrintersCalculator::BLACKLIST_ONLY &&
+      mode_val <= BulkPrintersCalculator::ALL_ACCESS) {
+    return static_cast<BulkPrintersCalculator::AccessMode>(mode_val);
+  }
+  // Error occurred, let's return the default value.
+  LOG(ERROR) << "Unrecognized access mode";
+  return BulkPrintersCalculator::ALL_ACCESS;
+}
+
+std::vector<std::string> ConvertToVector(const base::ListValue* list) {
+  std::vector<std::string> string_list;
+  if (list) {
+    for (const base::Value& value : *list) {
+      if (value.is_string()) {
+        string_list.push_back(value.GetString());
+      }
+    }
+  }
+  return string_list;
+}
+
+class CalculatorsPoliciesBinderImpl : public CalculatorsPoliciesBinder {
+ public:
+  CalculatorsPoliciesBinderImpl(CrosSettings* settings, Profile* profile)
+      : settings_(settings), profile_(profile) {
+    pref_change_registrar_.Init(profile->GetPrefs());
+    // Bind device policies to corresponding instance of BulkPrintersCalculator.
+    device_printers_ = BulkPrintersCalculatorFactory::Get()->GetForDevice();
+    if (device_printers_ && ++(BindingsCount()[device_printers_.get()]) == 1) {
+      BindSettings(kDeviceNativePrintersAccessMode,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceAccessMode);
+      BindSettings(kDeviceNativePrintersBlacklist,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceBlacklist);
+      BindSettings(kDeviceNativePrintersWhitelist,
+                   &CalculatorsPoliciesBinderImpl::UpdateDeviceWhitelist);
+    }
+    // Bind user policies to corresponding instance of BulkPrintersCalculator.
+    user_printers_ =
+        BulkPrintersCalculatorFactory::Get()->GetForProfile(profile);
+    if (user_printers_ && ++(BindingsCount()[user_printers_.get()]) == 1) {
+      BindPref(prefs::kRecommendedNativePrintersAccessMode,
+               &CalculatorsPoliciesBinderImpl::UpdateUserAccessMode);
+      BindPref(prefs::kRecommendedNativePrintersBlacklist,
+               &CalculatorsPoliciesBinderImpl::UpdateUserBlacklist);
+      BindPref(prefs::kRecommendedNativePrintersWhitelist,
+               &CalculatorsPoliciesBinderImpl::UpdateUserWhitelist);
+    }
+  }
+
+  ~CalculatorsPoliciesBinderImpl() override {
+    // We have to decrease counters in bindings_count.
+    if (device_printers_ && --(BindingsCount()[device_printers_.get()]) == 0) {
+      BindingsCount().erase(device_printers_.get());
+    }
+    if (user_printers_ && --(BindingsCount()[user_printers_.get()]) == 0) {
+      BindingsCount().erase(user_printers_.get());
+    }
+  }
+
+ private:
+  // Methods propagating values from policies to BulkPrintersCalculator.
+  void UpdateDeviceAccessMode() {
+    int mode_val;
+    if (!settings_->GetInteger(kDeviceNativePrintersAccessMode, &mode_val)) {
+      mode_val = BulkPrintersCalculator::AccessMode::UNSET;
+    }
+    device_printers_->SetAccessMode(ConvertToAccessMode(mode_val));
+  }
+
+  void UpdateDeviceBlacklist() {
+    device_printers_->SetBlacklist(
+        FromSettings(kDeviceNativePrintersBlacklist));
+  }
+
+  void UpdateDeviceWhitelist() {
+    device_printers_->SetWhitelist(
+        FromSettings(kDeviceNativePrintersWhitelist));
+  }
+
+  void UpdateUserAccessMode() {
+    user_printers_->SetAccessMode(
+        ConvertToAccessMode(profile_->GetPrefs()->GetInteger(
+            prefs::kRecommendedNativePrintersAccessMode)));
+  }
+
+  void UpdateUserBlacklist() {
+    user_printers_->SetBlacklist(
+        FromPrefs(prefs::kRecommendedNativePrintersBlacklist));
+  }
+
+  void UpdateUserWhitelist() {
+    user_printers_->SetWhitelist(
+        FromPrefs(prefs::kRecommendedNativePrintersWhitelist));
+  }
+
+  typedef void (CalculatorsPoliciesBinderImpl::*SimpleMethod)();
+
+  // Binds given device policy to given method and calls this method once.
+  void BindPref(const char* policy_name, SimpleMethod method_to_call) {
+    pref_change_registrar_.Add(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this)));
+    (this->*method_to_call)();
+  }
+
+  // Binds given user policy to given method and calls this method once.
+  void BindSettings(const char* policy_name, SimpleMethod method_to_call) {
+    subscriptions_.push_back(settings_->AddSettingsObserver(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this))));
+    (this->*method_to_call)();
+  }
+
+  // Extracts the list of strings named |policy_name| from device policies.
+  std::vector<std::string> FromSettings(const std::string& policy_name) {
+    const base::ListValue* list;
+    if (!settings_->GetList(policy_name, &list)) {
+      list = nullptr;
+    }
+    return ConvertToVector(list);
+  }
+
+  // Extracts the list of strings named |policy_name| from user policies.
+  std::vector<std::string> FromPrefs(const std::string& policy_name) {
+    return ConvertToVector(profile_->GetPrefs()->GetList(policy_name));
+  }
+
+  // Device and user bulk printers. Unowned.
+  base::WeakPtr<BulkPrintersCalculator> device_printers_;
+  base::WeakPtr<BulkPrintersCalculator> user_printers_;
+
+  // Device and profile (user) settings.
+  CrosSettings* settings_;
+  std::list<std::unique_ptr<CrosSettings::ObserverSubscription>> subscriptions_;
+  Profile* profile_;
+  PrefChangeRegistrar pref_change_registrar_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(CalculatorsPoliciesBinderImpl);
+};
+
+}  // namespace
+
+// static
+void CalculatorsPoliciesBinder::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  // Default value for access mode is AllAccess.
+  registry->RegisterIntegerPref(prefs::kRecommendedNativePrintersAccessMode,
+                                BulkPrintersCalculator::ALL_ACCESS);
+  registry->RegisterListPref(prefs::kRecommendedNativePrintersBlacklist);
+  registry->RegisterListPref(prefs::kRecommendedNativePrintersWhitelist);
+}
+
+// static
+std::unique_ptr<CalculatorsPoliciesBinder> CalculatorsPoliciesBinder::Create(
+    CrosSettings* settings,
+    Profile* profile) {
+  return std::make_unique<CalculatorsPoliciesBinderImpl>(settings, profile);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/calculators_policies_binder.h b/chrome/browser/chromeos/printing/calculators_policies_binder.h
new file mode 100644
index 0000000..926e840a0
--- /dev/null
+++ b/chrome/browser/chromeos/printing/calculators_policies_binder.h
@@ -0,0 +1,39 @@
+// 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_CHROMEOS_PRINTING_CALCULATORS_POLICIES_BINDER_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_CALCULATORS_POLICIES_BINDER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace chromeos {
+
+// Observes device settings & user profile modifications and propagates them to
+// BulkPrintersCalculator objects associated with given device context and user
+// profile. All methods must be called from the same sequence (UI).
+class CalculatorsPoliciesBinder {
+ public:
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // |settings| is the source of device policies. |profile| is a user profile.
+  static std::unique_ptr<CalculatorsPoliciesBinder> Create(
+      CrosSettings* settings,
+      Profile* profile);
+  virtual ~CalculatorsPoliciesBinder() = default;
+
+ protected:
+  CalculatorsPoliciesBinder() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CalculatorsPoliciesBinder);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_CALCULATORS_POLICIES_BINDER_H_
diff --git a/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc b/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
index e4908e3..93d794f4 100644
--- a/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
+++ b/chrome/browser/chromeos/printing/cups_print_job_manager_impl.cc
@@ -487,7 +487,7 @@
     jobs_.clear();
   }
 
-  // Notify observers that a state update has occured for |job|.
+  // Notify observers that a state update has occurred for |job|.
   void NotifyJobStateUpdate(base::WeakPtr<CupsPrintJob> job) {
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.cc b/chrome/browser/chromeos/printing/cups_printers_manager.cc
index 1faee11..c389831c 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.cc
@@ -111,8 +111,9 @@
     // Prime the printer cache with the configured and enterprise printers.
     printers_[kConfigured] = synced_printers_manager_->GetConfiguredPrinters();
     RebuildConfiguredPrintersIndex();
-    printers_[kEnterprise] = synced_printers_manager_->GetEnterprisePrinters();
     synced_printers_manager_observer_.Add(synced_printers_manager_);
+    enterprise_printers_are_ready_ =
+        synced_printers_manager_->GetEnterprisePrinters(printers_[kEnterprise]);
 
     // Callbacks may ensue immediately when the observer proxies are set up, so
     // these instantiations must come after everything else is initialized.
@@ -197,6 +198,9 @@
   void AddObserver(CupsPrintersManager::Observer* observer) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
     observer_list_.AddObserver(observer);
+    if (enterprise_printers_are_ready_) {
+      observer->OnEnterprisePrintersInitialized();
+    }
   }
 
   // Public API function.
@@ -258,9 +262,16 @@
 
   // SyncedPrintersManager::Observer implementation
   void OnEnterprisePrintersChanged(
-      const std::vector<Printer>& printers) override {
+      const std::vector<Printer>& printers,
+      bool enterprise_printers_are_ready) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
     printers_[kEnterprise] = printers;
+    if (enterprise_printers_are_ready && !enterprise_printers_are_ready_) {
+      enterprise_printers_are_ready_ = true;
+      for (auto& observer : observer_list_) {
+        observer.OnEnterprisePrintersInitialized();
+      }
+    }
     NotifyObservers({kEnterprise});
   }
 
@@ -548,6 +559,13 @@
   // Categorized printers.  This is indexed by PrinterClass.
   std::vector<std::vector<Printer>> printers_;
 
+  // Equals true if the list of enterprise printers and related policies
+  // is initialized and configured correctly.
+  bool enterprise_printers_are_ready_ = false;
+
+  // Printer ids that occur in one of our categories or printers.
+  std::unordered_set<std::string> known_printer_ids_;
+
   // This is a dual-purpose structure.  The keys in the map are printer ids.
   // If an entry exists in this map it means we have received a response from
   // PpdProvider about a PpdReference for the given printer.  A null value
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.h b/chrome/browser/chromeos/printing/cups_printers_manager.h
index b03ab16..441a71db 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.h
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.h
@@ -45,9 +45,13 @@
    public:
     // The list of printers in this class has changed to the given printers.
     virtual void OnPrintersChanged(PrinterClass printer_class,
-                                   const std::vector<Printer>& printers) = 0;
+                                   const std::vector<Printer>& printers) {}
+    // It is called exactly once for each observer. It means that the
+    // subsystem for enterprise printers is initialized. When an observer is
+    // being registered after the subsystem's initialization, this call is
+    // scheduled immediately in AddObserver method.
+    virtual void OnEnterprisePrintersInitialized() {}
 
-   protected:
     virtual ~Observer() = default;
   };
 
@@ -90,9 +94,9 @@
   // the printer_id is not that of a configured printer.
   virtual void RemoveConfiguredPrinter(const std::string& printer_id) = 0;
 
-  // Add or remove observers.  Observers do not need to be on the same
+  // Add or remove observers.  Observers must be on the same
   // sequence as the CupsPrintersManager.  Callbacks for a given observer
-  // will be on the same sequence as was used to call AddObserver().
+  // will be on the same sequence as the CupsPrintersManager.
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
index 76f234c..9861b24 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager_unittest.cc
@@ -36,8 +36,9 @@
   }
 
   // Returns printers from enterprise policy.
-  std::vector<Printer> GetEnterprisePrinters() const override {
-    return enterprise_printers_;
+  bool GetEnterprisePrinters(std::vector<Printer>& printers) const override {
+    printers = enterprise_printers_;
+    return true;
   }
 
   // Attach |observer| for notification of events.  |observer| is expected to
@@ -128,7 +129,7 @@
     enterprise_printers_.insert(enterprise_printers_.end(), printers.begin(),
                                 printers.end());
     for (Observer& observer : observers_) {
-      observer.OnEnterprisePrintersChanged(enterprise_printers_);
+      observer.OnEnterprisePrintersChanged(enterprise_printers_, true);
     }
   }
 
@@ -137,7 +138,7 @@
   void RemoveEnterprisePrinters(const std::unordered_set<std::string>& ids) {
     RemovePrinters(ids, &enterprise_printers_);
     for (Observer& observer : observers_) {
-      observer.OnEnterprisePrintersChanged(enterprise_printers_);
+      observer.OnEnterprisePrintersChanged(enterprise_printers_, true);
     }
   }
 
diff --git a/chrome/browser/chromeos/printing/device_external_printers_factory.cc b/chrome/browser/chromeos/printing/device_external_printers_factory.cc
deleted file mode 100644
index f85bb38..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_factory.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-
-namespace chromeos {
-
-namespace {
-
-base::LazyInstance<DeviceExternalPrintersFactory>::DestructorAtExit
-    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-// static
-DeviceExternalPrintersFactory* DeviceExternalPrintersFactory::Get() {
-  return g_printers_factory.Pointer();
-}
-
-base::WeakPtr<ExternalPrinters> DeviceExternalPrintersFactory::GetForDevice() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!device_printers_)
-    device_printers_ = ExternalPrinters::Create();
-  return device_printers_->AsWeakPtr();
-}
-
-void DeviceExternalPrintersFactory::Shutdown() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  device_printers_.reset();
-}
-
-DeviceExternalPrintersFactory::DeviceExternalPrintersFactory() = default;
-DeviceExternalPrintersFactory::~DeviceExternalPrintersFactory() = default;
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/device_external_printers_factory.h b/chrome/browser/chromeos/printing/device_external_printers_factory.h
deleted file mode 100644
index e73b311d..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_factory.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-
-namespace chromeos {
-
-// Dispenses ExternalPrinters object for device. Access to this object should be
-// sequenced.
-class DeviceExternalPrintersFactory {
- public:
-  static DeviceExternalPrintersFactory* Get();
-
-  // Returns a WeakPtr to the ExternalPrinters registered for the device.
-  base::WeakPtr<ExternalPrinters> GetForDevice();
-
-  // Tear down device ExternalPrinters object.
-  void Shutdown();
-
- private:
-  friend struct base::LazyInstanceTraitsBase<DeviceExternalPrintersFactory>;
-
-  DeviceExternalPrintersFactory();
-  ~DeviceExternalPrintersFactory();
-
-  std::unique_ptr<ExternalPrinters> device_printers_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(DeviceExternalPrintersFactory);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc b/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc
deleted file mode 100644
index dfdabd88..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h"
-
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/values.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-
-namespace chromeos {
-
-namespace {
-
-// Extracts the list of strings named |policy_name| from |settings| and returns
-// it.
-std::vector<std::string> FromSettings(const CrosSettings* settings,
-                                      const std::string& policy_name) {
-  std::vector<std::string> string_list;
-  const base::ListValue* list = nullptr;
-  if (!settings->GetList(policy_name, &list) || !list) {
-    return string_list;
-  }
-
-  for (const base::Value& value : *list) {
-    if (value.is_string()) {
-      string_list.push_back(value.GetString());
-    }
-  }
-
-  return string_list;
-}
-
-}  // namespace
-
-DeviceExternalPrintersSettingsBridge::DeviceExternalPrintersSettingsBridge(
-    const ExternalPrinterPolicies& policies,
-    CrosSettings* settings)
-    : settings_(settings), policies_(policies) {
-  access_mode_subscription_ = settings->AddSettingsObserver(
-      policies_.access_mode,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::AccessModeUpdated,
-          base::Unretained(this)));
-  blacklist_subscription_ = settings->AddSettingsObserver(
-      policies_.blacklist,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::BlacklistUpdated,
-          base::Unretained(this)));
-  whitelist_subscription_ = settings->AddSettingsObserver(
-      policies_.whitelist,
-      base::BindRepeating(
-          &DeviceExternalPrintersSettingsBridge::WhitelistUpdated,
-          base::Unretained(this)));
-  Initialize();
-}
-
-DeviceExternalPrintersSettingsBridge::~DeviceExternalPrintersSettingsBridge() =
-    default;
-
-void DeviceExternalPrintersSettingsBridge::Initialize() {
-  BlacklistUpdated();
-  WhitelistUpdated();
-  AccessModeUpdated();
-}
-
-void DeviceExternalPrintersSettingsBridge::AccessModeUpdated() {
-  int mode_val;
-  // Settings should contain value for access mode device setting.
-  // Even if it's not pushed with device policy, the default value should be
-  // set.
-  CHECK(settings_->GetInteger(policies_.access_mode, &mode_val));
-  ExternalPrinters::AccessMode mode =
-      static_cast<ExternalPrinters::AccessMode>(mode_val);
-
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetAccessMode(mode);
-}
-
-void DeviceExternalPrintersSettingsBridge::BlacklistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetBlacklist(FromSettings(settings_, policies_.blacklist));
-}
-
-void DeviceExternalPrintersSettingsBridge::WhitelistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      DeviceExternalPrintersFactory::Get()->GetForDevice();
-  if (printers)
-    printers->SetWhitelist(FromSettings(settings_, policies_.whitelist));
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h b/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h
deleted file mode 100644
index 49e908d..0000000
--- a/chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
-
-#include "base/macros.h"
-#include "chrome/browser/chromeos/printing/external_printers_policies.h"
-#include "chrome/browser/chromeos/settings/cros_settings.h"
-
-namespace chromeos {
-
-class ExternalPrinters;
-
-// Observe device settings changes and propagate changes to ExternalPrinters.
-class DeviceExternalPrintersSettingsBridge {
- public:
-  DeviceExternalPrintersSettingsBridge(const ExternalPrinterPolicies& policies,
-                                       CrosSettings* settings);
-  ~DeviceExternalPrintersSettingsBridge();
-
- private:
-  // Retrieve initial values for device settings.
-  void Initialize();
-
-  // Handle update for the access mode policy.
-  void AccessModeUpdated();
-
-  // Handle updates for the blacklist policy.
-  void BlacklistUpdated();
-
-  // Handle updates for the whitelist policy.
-  void WhitelistUpdated();
-
-  CrosSettings* settings_;
-  const ExternalPrinterPolicies policies_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> access_mode_subscription_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> blacklist_subscription_;
-  std::unique_ptr<CrosSettings::ObserverSubscription> whitelist_subscription_;
-
-  DISALLOW_COPY_AND_ASSIGN(DeviceExternalPrintersSettingsBridge);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_DEVICE_EXTERNAL_PRINTERS_SETTINGS_BRIDGE_H_
diff --git a/chrome/browser/chromeos/printing/enterprise_printers_provider.cc b/chrome/browser/chromeos/printing/enterprise_printers_provider.cc
new file mode 100644
index 0000000..7f6015d
--- /dev/null
+++ b/chrome/browser/chromeos/printing/enterprise_printers_provider.cc
@@ -0,0 +1,255 @@
+// 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/chromeos/printing/enterprise_printers_provider.h"
+
+#include <list>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/json/json_reader.h"
+#include "base/md5.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator.h"
+#include "chrome/browser/chromeos/printing/bulk_printers_calculator_factory.h"
+#include "chrome/browser/chromeos/printing/calculators_policies_binder.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/policy/profile_policy_connector_factory.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/printing/printer_translator.h"
+#include "components/policy/core/common/policy_service.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+
+namespace {
+
+std::vector<std::string> ConvertToVector(const base::ListValue* list) {
+  std::vector<std::string> string_list;
+  if (list) {
+    for (const base::Value& value : *list) {
+      if (value.is_string()) {
+        string_list.push_back(value.GetString());
+      }
+    }
+  }
+  return string_list;
+}
+
+class EnterprisePrintersProviderImpl : public EnterprisePrintersProvider,
+                                       public BulkPrintersCalculator::Observer {
+ public:
+  EnterprisePrintersProviderImpl(CrosSettings* settings, Profile* profile)
+      : profile_(profile) {
+    // initialization of pref_change_registrar
+    pref_change_registrar_.Init(profile->GetPrefs());
+
+    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
+      // Binds instances of BulkPrintersCalculator to policies.
+      policies_binder_ = CalculatorsPoliciesBinder::Create(settings, profile);
+      // Get instance of BulkPrintersCalculator for device policies.
+      device_printers_ = BulkPrintersCalculatorFactory::Get()->GetForDevice();
+      if (device_printers_) {
+        device_printers_->AddObserver(this);
+        RecalculateCompleteFlagForDevicePrinters();
+      }
+      // Get instance of BulkPrintersCalculator for user policies.
+      user_printers_ =
+          BulkPrintersCalculatorFactory::Get()->GetForProfile(profile);
+      if (user_printers_) {
+        user_printers_->AddObserver(this);
+        RecalculateCompleteFlagForUserPrinters();
+      }
+    } else {
+      // If a "Bulk Printers" feature is inactive, we do not bind anything.
+      // The list of printers is always empty and is reported as complete.
+      complete_ = true;
+    }
+    // Binds policy with recommended printers (deprecated). This method calls
+    // indirectly RecalculateCurrentPrintersList() that prepares the first
+    // version of final list of printers.
+    BindPref(prefs::kRecommendedNativePrinters,
+             &EnterprisePrintersProviderImpl::UpdateUserRecommendedPrinters);
+  }
+
+  ~EnterprisePrintersProviderImpl() override {
+    if (device_printers_)
+      device_printers_->RemoveObserver(this);
+    if (user_printers_)
+      user_printers_->RemoveObserver(this);
+  }
+
+  void AddObserver(EnterprisePrintersProvider::Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.AddObserver(observer);
+    observer->OnPrintersChanged(complete_, printers_);
+  }
+
+  void RemoveObserver(EnterprisePrintersProvider::Observer* observer) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    observers_.RemoveObserver(observer);
+  }
+
+  // BulkPrintersCalculator::Observer implementation
+  void OnPrintersChanged(const BulkPrintersCalculator* sender) override {
+    if (sender == device_printers_.get()) {
+      RecalculateCompleteFlagForDevicePrinters();
+    } else {
+      RecalculateCompleteFlagForUserPrinters();
+    }
+    RecalculateCurrentPrintersList();
+  }
+
+ private:
+  // This method process value from the deprecated policy with recommended
+  // printers. It is called when value of the policy changes.
+  void UpdateUserRecommendedPrinters() {
+    recommended_printers_.clear();
+    std::vector<std::string> data =
+        FromPrefs(prefs::kRecommendedNativePrinters);
+    for (const auto& printer_json : data) {
+      std::unique_ptr<base::DictionaryValue> printer_dictionary =
+          base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
+              printer_json, base::JSON_ALLOW_TRAILING_COMMAS));
+      if (!printer_dictionary) {
+        LOG(WARNING) << "Ignoring invalid printer.  Invalid JSON object: "
+                     << printer_json;
+        continue;
+      }
+
+      // Policy printers don't have id's but the ids only need to be locally
+      // unique so we'll hash the record.  This will not collide with the
+      // UUIDs generated for user entries.
+      std::string id = base::MD5String(printer_json);
+      printer_dictionary->SetString(kPrinterId, id);
+
+      auto new_printer = RecommendedPrinterToPrinter(*printer_dictionary);
+      if (!new_printer) {
+        LOG(WARNING) << "Recommended printer is malformed.";
+        continue;
+      }
+
+      if (!recommended_printers_.insert({id, *new_printer}).second) {
+        // Printer is already in the list.
+        LOG(WARNING) << "Duplicate printer ignored: " << id;
+        continue;
+      }
+    }
+    RecalculateCurrentPrintersList();
+  }
+
+  // These three methods calculate resultant list of printers and complete flag.
+
+  void RecalculateCompleteFlagForUserPrinters() {
+    user_printers_is_complete_ =
+        user_printers_->IsComplete() &&
+        (user_printers_->IsDataPolicySet() ||
+         !PolicyWithDataIsSet(policy::key::kNativePrintersBulkConfiguration));
+  }
+
+  void RecalculateCompleteFlagForDevicePrinters() {
+    device_printers_is_complete_ =
+        device_printers_->IsComplete() &&
+        (device_printers_->IsDataPolicySet() ||
+         !PolicyWithDataIsSet(policy::key::kDeviceNativePrinters));
+  }
+
+  void RecalculateCurrentPrintersList() {
+    complete_ = true;
+    printers_ = recommended_printers_;
+    if (device_printers_) {
+      complete_ = complete_ && device_printers_is_complete_;
+      const auto& printers = device_printers_->GetPrinters();
+      printers_.insert(printers.begin(), printers.end());
+    }
+    if (user_printers_) {
+      complete_ = complete_ && user_printers_is_complete_;
+      const auto& printers = user_printers_->GetPrinters();
+      printers_.insert(printers.begin(), printers.end());
+    }
+    for (auto& observer : observers_) {
+      observer.OnPrintersChanged(complete_, printers_);
+    }
+  }
+
+  typedef void (EnterprisePrintersProviderImpl::*SimpleMethod)();
+
+  // Binds given user policy to given method and calls this method once.
+  void BindPref(const char* policy_name, SimpleMethod method_to_call) {
+    pref_change_registrar_.Add(
+        policy_name,
+        base::BindRepeating(method_to_call, base::Unretained(this)));
+    (this->*method_to_call)();
+  }
+
+  // Extracts the list of strings named |policy_name| from user policies.
+  std::vector<std::string> FromPrefs(const std::string& policy_name) {
+    return ConvertToVector(profile_->GetPrefs()->GetList(policy_name));
+  }
+
+  // Checks if given policy is set and if it is a dictionary
+  bool PolicyWithDataIsSet(const char* policy_name) {
+    policy::ProfilePolicyConnector* policy_connector =
+        policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile_);
+    if (!policy_connector) {
+      // something is wrong
+      return false;
+    }
+    const policy::PolicyNamespace policy_namespace =
+        policy::PolicyNamespace(policy::PolicyDomain::POLICY_DOMAIN_CHROME, "");
+    const policy::PolicyMap& policy_map =
+        policy_connector->policy_service()->GetPolicies(policy_namespace);
+    const base::Value* value = policy_map.GetValue(policy_name);
+    if (value && value->is_dict()) {
+      // policy is set and its value is a dictionary
+      return true;
+    }
+    return false;
+  }
+
+  // current partial results
+  std::unordered_map<std::string, Printer> recommended_printers_;
+  bool device_printers_is_complete_ = true;
+  bool user_printers_is_complete_ = true;
+
+  // current final results
+  bool complete_ = false;
+  std::unordered_map<std::string, Printer> printers_;
+
+  // Calculators for bulk printers from device and user policies. Unowned.
+  base::WeakPtr<BulkPrintersCalculator> device_printers_;
+  base::WeakPtr<BulkPrintersCalculator> user_printers_;
+
+  // Policies binder (bridge between policies and calculators). Owned.
+  std::unique_ptr<CalculatorsPoliciesBinder> policies_binder_;
+
+  // Profile (user) settings.
+  Profile* profile_;
+  PrefChangeRegistrar pref_change_registrar_;
+
+  base::ObserverList<EnterprisePrintersProvider::Observer>::Unchecked
+      observers_;
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(EnterprisePrintersProviderImpl);
+};
+
+}  // namespace
+
+// static
+void EnterprisePrintersProvider::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterListPref(prefs::kRecommendedNativePrinters);
+  CalculatorsPoliciesBinder::RegisterProfilePrefs(registry);
+}
+
+// static
+std::unique_ptr<EnterprisePrintersProvider> EnterprisePrintersProvider::Create(
+    CrosSettings* settings,
+    Profile* profile) {
+  return std::make_unique<EnterprisePrintersProviderImpl>(settings, profile);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/enterprise_printers_provider.h b/chrome/browser/chromeos/printing/enterprise_printers_provider.h
new file mode 100644
index 0000000..cdd4210f
--- /dev/null
+++ b/chrome/browser/chromeos/printing/enterprise_printers_provider.h
@@ -0,0 +1,61 @@
+// 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_CHROMEOS_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
+#define CHROME_BROWSER_CHROMEOS_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chromeos/printing/printer_configuration.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace chromeos {
+
+// Uses classes BulkPrintersCalculator and CalculatorsPoliciesBinder to track
+// device settings & user profile modifications and to calculates resultant
+// list of available enterprise printers. The final list of available enterprise
+// printers is propagated to Observers.
+// All methods must be called from the same sequence (UI) and all observers'
+// notifications will be called from this sequence.
+class EnterprisePrintersProvider {
+ public:
+  class Observer {
+   public:
+    // |complete| is true if all policies have been parsed and applied (even
+    // when parsing errors occurred), false means that a new list of available
+    // printers is being calculated. |printers| contains the current list of
+    // available printers: the map is indexed by printers ids. This
+    // notification is called when value of any of these two parameters changes.
+    virtual void OnPrintersChanged(
+        bool complete,
+        const std::unordered_map<std::string, Printer>& printers) = 0;
+  };
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // |settings| is the source of device policies. |profile| is a user profile.
+  static std::unique_ptr<EnterprisePrintersProvider> Create(
+      CrosSettings* settings,
+      Profile* profile);
+  virtual ~EnterprisePrintersProvider() = default;
+
+  // This method also calls directly OnPrintersChanged(...) from |observer|.
+  virtual void AddObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+
+ protected:
+  EnterprisePrintersProvider() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnterprisePrintersProvider);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_ENTERPRISE_PRINTERS_PROVIDER_H_
diff --git a/chrome/browser/chromeos/printing/external_printers.cc b/chrome/browser/chromeos/printing/external_printers.cc
deleted file mode 100644
index eee26f31..0000000
--- a/chrome/browser/chromeos/printing/external_printers.cc
+++ /dev/null
@@ -1,374 +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/chromeos/printing/external_printers.h"
-
-#include <set>
-
-#include "base/bind.h"
-#include "base/feature_list.h"
-#include "base/json/json_reader.h"
-#include "base/memory/weak_ptr.h"
-#include "base/observer_list.h"
-#include "base/sequence_checker.h"
-#include "base/sequenced_task_runner.h"
-#include "base/stl_util.h"
-#include "base/task/post_task.h"
-#include "base/task_runner_util.h"
-#include "base/threading/scoped_blocking_call.h"
-#include "base/values.h"
-#include "chrome/common/chrome_features.h"
-#include "chromeos/printing/printer_translator.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr int kMaxRecords = 20000;
-
-using PrinterCache = std::vector<std::unique_ptr<Printer>>;
-using PrinterView = std::map<const std::string, const Printer>;
-
-// Parses |data|, a JSON blob, into a vector of Printers.  If |data| cannot be
-// parsed, returns nullptr.  This is run off the UI thread as it could be very
-// slow.
-std::unique_ptr<PrinterCache> ParsePrinters(std::unique_ptr<std::string> data) {
-  int error_code = 0;
-  int error_line = 0;
-
-  // This could be really slow.
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  std::unique_ptr<base::Value> json_blob =
-      base::JSONReader::ReadAndReturnErrorDeprecated(
-          *data, base::JSONParserOptions::JSON_PARSE_RFC, &error_code,
-          nullptr /* error_msg_out */, &error_line);
-  // It's not valid JSON.  Give up.
-  if (!json_blob || !json_blob->is_list()) {
-    LOG(WARNING) << "Failed to parse printers policy (" << error_code
-                 << ") on line " << error_line;
-    return nullptr;
-  }
-
-  const base::Value::ListStorage& printer_list = json_blob->GetList();
-  if (printer_list.size() > kMaxRecords) {
-    LOG(WARNING) << "Too many records in printers policy: "
-                 << printer_list.size();
-    return nullptr;
-  }
-
-  auto parsed_printers = std::make_unique<PrinterCache>();
-  parsed_printers->reserve(printer_list.size());
-  for (const base::Value& val : printer_list) {
-    // TODO(skau): Convert to the new Value APIs.
-    const base::DictionaryValue* printer_dict;
-    if (!val.GetAsDictionary(&printer_dict)) {
-      LOG(WARNING) << "Entry in printers policy skipped.  Not a dictionary.";
-      continue;
-    }
-
-    auto printer = RecommendedPrinterToPrinter(*printer_dict);
-    if (!printer) {
-      LOG(WARNING) << "Failed to parse printer configuration.  Skipped.";
-      continue;
-    }
-    parsed_printers->push_back(std::move(printer));
-  }
-
-  return parsed_printers;
-}
-
-// Computes the effective printer list using the access mode and
-// blacklist/whitelist.  Methods are required to be sequenced.  This object is
-// the owner of all the policy data.
-class Restrictions {
- public:
-  Restrictions() : printers_cache_(nullptr) {
-    DETACH_FROM_SEQUENCE(sequence_checker_);
-  }
-  ~Restrictions() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
-
-  // Sets the printer cache using the policy blob |data|.  If the policy can be
-  // computed, returns the computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> SetData(std::unique_ptr<std::string> data) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::ScopedBlockingCall scoped_blocking_call(
-        FROM_HERE, base::BlockingType::MAY_BLOCK);
-    printers_cache_ = ParsePrinters(std::move(data));
-    return ComputePrinters();
-  }
-
-  // Clear the printer cache.  Computed lists will be invalid until we receive
-  // new data.
-  void ClearData() {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    printers_cache_.reset();
-  }
-
-  // Sets the access mode to |mode|.  If the policy can be computed, returns the
-  // computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateAccessMode(
-      ExternalPrinters::AccessMode mode) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    mode_ = mode;
-    return ComputePrinters();
-  }
-
-  // Sets the blacklist to |blacklist|.  If the policy can be computed, returns
-  // the computed list. Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateBlacklist(
-      const std::vector<std::string>& blacklist) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    has_blacklist_ = true;
-    blacklist_ = std::set<std::string>(blacklist.begin(), blacklist.end());
-    return ComputePrinters();
-  }
-
-  // Sets the whitelist to |whitelist|.  If the policy can be computed, returns
-  // the computed list.  Otherwise, nullptr.
-  std::unique_ptr<PrinterView> UpdateWhitelist(
-      const std::vector<std::string>& whitelist) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    has_whitelist_ = true;
-    whitelist_ = std::set<std::string>(whitelist.begin(), whitelist.end());
-    return ComputePrinters();
-  }
-
- private:
-  // Returns true if we have enough data to compute the effective printer list.
-  bool IsReady() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!printers_cache_) {
-      return false;
-    }
-
-    switch (mode_) {
-      case ExternalPrinters::AccessMode::ALL_ACCESS:
-        return true;
-      case ExternalPrinters::AccessMode::BLACKLIST_ONLY:
-        return has_blacklist_;
-      case ExternalPrinters::AccessMode::WHITELIST_ONLY:
-        return has_whitelist_;
-      case ExternalPrinters::AccessMode::UNSET:
-        return false;
-    }
-    NOTREACHED();
-    return false;
-  }
-
-  // Returns the effective printer list based on |mode_| from the entries in
-  // |printers_cache_|.
-  std::unique_ptr<PrinterView> ComputePrinters() {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-    if (!IsReady()) {
-      return nullptr;
-    }
-
-    auto view = std::make_unique<PrinterView>();
-    switch (mode_) {
-      case ExternalPrinters::UNSET:
-        NOTREACHED();
-        break;
-      case ExternalPrinters::WHITELIST_ONLY:
-        for (const auto& printer : *printers_cache_) {
-          if (base::ContainsKey(whitelist_, printer->id())) {
-            view->insert({printer->id(), *printer});
-          }
-        }
-        break;
-      case ExternalPrinters::BLACKLIST_ONLY:
-        for (const auto& printer : *printers_cache_) {
-          if (!base::ContainsKey(blacklist_, printer->id())) {
-            view->insert({printer->id(), *printer});
-          }
-        }
-        break;
-      case ExternalPrinters::ALL_ACCESS:
-        for (const auto& printer : *printers_cache_) {
-          view->insert({printer->id(), *printer});
-        }
-        break;
-    }
-
-    return view;
-  }
-
-  // Cache of the parsed printer configuration file.
-  std::unique_ptr<PrinterCache> printers_cache_;
-
-  // The type of restriction which is enforced.
-  ExternalPrinters::AccessMode mode_ = ExternalPrinters::UNSET;
-  // The list of ids which should not appear in the final list.
-  bool has_blacklist_ = false;
-  std::set<std::string> blacklist_;
-  // The list of the only ids which should appear in the final list.
-  bool has_whitelist_ = false;
-  std::set<std::string> whitelist_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-  DISALLOW_COPY_AND_ASSIGN(Restrictions);
-};
-
-class ExternalPrintersImpl : public ExternalPrinters {
- public:
-  ExternalPrintersImpl()
-      : restrictions_(std::make_unique<Restrictions>()),
-        restrictions_runner_(base::CreateSequencedTaskRunnerWithTraits(
-            {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
-             base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
-        weak_ptr_factory_(this) {}
-  ~ExternalPrintersImpl() override {
-    bool success =
-        restrictions_runner_->DeleteSoon(FROM_HERE, std::move(restrictions_));
-    if (!success) {
-      LOG(WARNING) << "Unable to schedule deletion of policy object.";
-    }
-  }
-
-  void AddObserver(Observer* observer) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.AddObserver(observer);
-  }
-
-  void RemoveObserver(Observer* observer) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    observers_.RemoveObserver(observer);
-  }
-
-  // Resets the printer state fields.
-  void ClearData() override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      return;
-    }
-
-    // Update restrictions then clear our local cache on return so we don't get
-    // out of sequence.
-    restrictions_runner_->PostTaskAndReply(
-        FROM_HERE,
-        base::BindOnce(&Restrictions::ClearData,
-                       base::Unretained(restrictions_.get())),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr(), nullptr));
-  }
-
-  void SetData(std::unique_ptr<std::string> data) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (!base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      return;
-    }
-
-    if (!data) {
-      LOG(WARNING) << "Received null data";
-      return;
-    }
-
-    // Forward data to Restrictions for computation.
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::SetData,
-                       base::Unretained(restrictions_.get()), std::move(data)),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetAccessMode(AccessMode mode) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateAccessMode,
-                       base::Unretained(restrictions_.get()), mode),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetBlacklist(const std::vector<std::string>& blacklist) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateBlacklist,
-                       base::Unretained(restrictions_.get()), blacklist),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  void SetWhitelist(const std::vector<std::string>& whitelist) override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    base::PostTaskAndReplyWithResult(
-        restrictions_runner_.get(), FROM_HERE,
-        base::BindOnce(&Restrictions::UpdateWhitelist,
-                       base::Unretained(restrictions_.get()), whitelist),
-        base::BindOnce(&ExternalPrintersImpl::OnComputationComplete,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  bool IsPolicySet() const override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return received_data_;
-  }
-
-  const std::map<const std::string, const Printer>& GetPrinters()
-      const override {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return printers_;
-  }
-
- private:
-  // Called on computation completion.  |view| is the computed printers which a
-  // user should be able to see.  If |view| is nullptr, it's taken to mean that
-  // the list is now invalid and will be cleared.
-  void OnComputationComplete(std::unique_ptr<PrinterView> view) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    bool valid;
-    if (!view) {
-      // Printers are dropped if parsing failed.  We can no longer determine
-      // what the domain owner wanted.
-      printers_.clear();
-      valid = false;
-    } else {
-      printers_.swap(*view);
-      valid = true;
-    }
-
-    // Maybe notify that the computed list has changed.
-    // Do not notify for invalid->invalid transitions
-    if (!valid && !received_data_) {
-      return;
-    }
-
-    received_data_ = valid;
-    for (auto& observer : observers_) {
-      // We rely on the assumption that this is sequenced with the rest of our
-      // code to guarantee that printers_ remains valid.
-      observer.OnPrintersChanged(received_data_, printers_);
-    }
-  }
-
-  // Holds the blacklist and whitelist.  Computes the effective printer list.
-  std::unique_ptr<Restrictions> restrictions_;
-  // Off UI sequence for computing the printer view.
-  scoped_refptr<base::SequencedTaskRunner> restrictions_runner_;
-
-  // True if printers_ is based on a current policy.
-  bool received_data_ = false;
-  // The computed set of printers.
-  PrinterView printers_;
-
-  base::ObserverList<ExternalPrinters::Observer>::Unchecked observers_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-  base::WeakPtrFactory<ExternalPrintersImpl> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersImpl);
-};
-
-}  // namespace
-
-// static
-std::unique_ptr<ExternalPrinters> ExternalPrinters::Create() {
-  return std::make_unique<ExternalPrintersImpl>();
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers.h b/chrome/browser/chromeos/printing/external_printers.h
deleted file mode 100644
index 81f0bb5..0000000
--- a/chrome/browser/chromeos/printing/external_printers.h
+++ /dev/null
@@ -1,85 +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_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/memory/weak_ptr.h"
-#include "chromeos/printing/printer_configuration.h"
-
-namespace chromeos {
-
-// Manages download and parsing of the external policy printer configuration and
-// enforces restrictions.
-class ExternalPrinters : public base::SupportsWeakPtr<ExternalPrinters> {
- public:
-  // Choose the policy for printer access.
-  enum AccessMode {
-    UNSET = -1,
-    // Printers in the blacklist are disallowed.  Others are allowed.
-    BLACKLIST_ONLY = 0,
-    // Only printers in the whitelist are allowed.
-    WHITELIST_ONLY = 1,
-    // All printers in the policy are allowed.
-    ALL_ACCESS = 2
-  };
-
-  // Observer is notified when the computed set of printers change.  It is
-  // assumed that the observer is on the same sequence as the object it is
-  // observing.
-  class Observer {
-   public:
-    // Called when the printers have changed and should be queried.  |valid| is
-    // true if |printers| is based on a valid policy.  |printers| are the
-    // printers that should be available to the user.
-    virtual void OnPrintersChanged(
-        bool valid,
-        const std::map<const std::string, const Printer>& printers) = 0;
-  };
-
-  // Creates a handler for the external printer policies.
-  static std::unique_ptr<ExternalPrinters> Create();
-
-  virtual ~ExternalPrinters() = default;
-
-  virtual void AddObserver(Observer* observer) = 0;
-  virtual void RemoveObserver(Observer* observer) = 0;
-
-  // Parses |data| which is the contents of the bulk printes file and extracts
-  // printer information.  The file format is assumed to be JSON.
-  virtual void SetData(std::unique_ptr<std::string> data) = 0;
-  // Removes all printer data and invalidates the configuration.
-  virtual void ClearData() = 0;
-
-  // Set the access mode which chooses the type of filtering that is performed.
-  virtual void SetAccessMode(AccessMode mode) = 0;
-  // Set the |blacklist| which excludes printers with the given id if access
-  // mode is BLACKLIST_ONLY.
-  virtual void SetBlacklist(const std::vector<std::string>& blacklist) = 0;
-  // Set the |whitelist| which is the complete list of printers that are show to
-  // the user.  This is in effect if access mode is WHITELIST_ONLY.
-  virtual void SetWhitelist(const std::vector<std::string>& whitelist) = 0;
-
-  // Returns true if the printer map has been computed from a valid policy.
-  // Returns false otherwise.  This is computed asynchronously.  Observe
-  // OnPrintersChanged() to be notified when it is updated.  This may never
-  // become true if a user does not have the appropriate printer policies.
-  virtual bool IsPolicySet() const = 0;
-
-  // Returns a refernce to a map of the computed set of printers.  The map is
-  // empty if either the policy was empty or we are yet to receive the full
-  // policy. Use IsPolicySet() to differentiate betweeen the two.
-  virtual const std::map<const std::string, const Printer>& GetPrinters()
-      const = 0;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_factory.cc b/chrome/browser/chromeos/printing/external_printers_factory.cc
deleted file mode 100644
index b4f311d..0000000
--- a/chrome/browser/chromeos/printing/external_printers_factory.cc
+++ /dev/null
@@ -1,65 +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/chromeos/printing/external_printers_factory.h"
-
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "components/user_manager/user.h"
-
-namespace chromeos {
-
-namespace {
-
-base::LazyInstance<ExternalPrintersFactory>::DestructorAtExit
-    g_printers_factory = LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-// static
-ExternalPrintersFactory* ExternalPrintersFactory::Get() {
-  return g_printers_factory.Pointer();
-}
-
-base::WeakPtr<ExternalPrinters> ExternalPrintersFactory::GetForAccountId(
-    const AccountId& account_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  auto found = printers_by_user_.find(account_id);
-  if (found != printers_by_user_.end()) {
-    return found->second->AsWeakPtr();
-  }
-
-  printers_by_user_[account_id] = ExternalPrinters::Create();
-  return printers_by_user_[account_id]->AsWeakPtr();
-}
-
-base::WeakPtr<ExternalPrinters> ExternalPrintersFactory::GetForProfile(
-    Profile* profile) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  const user_manager::User* user =
-      ProfileHelper::Get()->GetUserByProfile(profile);
-  if (!user)
-    return nullptr;
-
-  return GetForAccountId(user->GetAccountId());
-}
-
-void ExternalPrintersFactory::RemoveForUserId(const AccountId& account_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  printers_by_user_.erase(account_id);
-}
-
-void ExternalPrintersFactory::Shutdown() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  printers_by_user_.clear();
-}
-
-ExternalPrintersFactory::ExternalPrintersFactory() = default;
-ExternalPrintersFactory::~ExternalPrintersFactory() = default;
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers_factory.h b/chrome/browser/chromeos/printing/external_printers_factory.h
deleted file mode 100644
index c86d990..0000000
--- a/chrome/browser/chromeos/printing/external_printers_factory.h
+++ /dev/null
@@ -1,59 +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_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
-
-#include <map>
-#include <memory>
-
-#include "base/lazy_instance.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "components/account_id/account_id.h"
-
-class Profile;
-
-namespace chromeos {
-
-// Dispenses ExternalPrinters objects based on account id.  Access to this
-// object should be sequenced.
-class ExternalPrintersFactory {
- public:
-  static ExternalPrintersFactory* Get();
-
-  // Returns a WeakPtr to the ExternalPrinters registered for |account_id|. If
-  // an ExternalPrinters does not exist, one will be created for |account_id|.
-  // The returned object remains valid until RemoveForUserId or Shutdown is
-  // called.
-  base::WeakPtr<ExternalPrinters> GetForAccountId(const AccountId& account_id);
-
-  // Returns a WeakPtr to the ExternalPrinters registered for |profile| which
-  // could be null if |profile| does not map to a valid AccountId. The returned
-  // object remains valid until RemoveForUserId or Shutdown is called.
-  base::WeakPtr<ExternalPrinters> GetForProfile(Profile* profile);
-
-  // Deletes the ExternalPrinters registered for |account_id|.
-  void RemoveForUserId(const AccountId& account_id);
-
-  // Tear down all ExternalPrinters.
-  void Shutdown();
-
- private:
-  friend struct base::LazyInstanceTraitsBase<ExternalPrintersFactory>;
-
-  ExternalPrintersFactory();
-  ~ExternalPrintersFactory();
-
-  std::map<AccountId, std::unique_ptr<ExternalPrinters>> printers_by_user_;
-  SEQUENCE_CHECKER(sequence_checker_);
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersFactory);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_FACTORY_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_policies.h b/chrome/browser/chromeos/printing/external_printers_policies.h
deleted file mode 100644
index 2b895ab..0000000
--- a/chrome/browser/chromeos/printing/external_printers_policies.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
-
-#include <string>
-
-// A collection of preference names representing the external printer fields.
-struct ExternalPrinterPolicies {
-  std::string access_mode;
-  std::string blacklist;
-  std::string whitelist;
-};
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_POLICIES_H_
diff --git a/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc b/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc
deleted file mode 100644
index 237ac8d..0000000
--- a/chrome/browser/chromeos/printing/external_printers_pref_bridge.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/printing/external_printers_pref_bridge.h"
-
-#include <string>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/values.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/common/pref_names.h"
-#include "components/policy/policy_constants.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/pref_service.h"
-
-namespace chromeos {
-
-namespace {
-
-// Extracts the list of strings named |policy_name| from |prefs| and returns it.
-std::vector<std::string> FromPrefs(const PrefService* prefs,
-                                   const std::string& policy_name) {
-  std::vector<std::string> string_list;
-  const base::ListValue* list = prefs->GetList(policy_name);
-  for (const base::Value& value : *list) {
-    if (value.is_string()) {
-      string_list.push_back(value.GetString());
-    }
-  }
-
-  return string_list;
-}
-
-}  // namespace
-
-// static
-void ExternalPrintersPrefBridge::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry,
-    const ExternalPrinterPolicies& policies) {
-  // Default value for access mode is AllAccess.
-  registry->RegisterIntegerPref(policies.access_mode,
-                                ExternalPrinters::ALL_ACCESS);
-  registry->RegisterListPref(policies.blacklist);
-  registry->RegisterListPref(policies.whitelist);
-}
-
-ExternalPrintersPrefBridge::ExternalPrintersPrefBridge(
-    const ExternalPrinterPolicies& policies,
-    Profile* profile)
-    : profile_(profile), policies_(policies) {
-  pref_change_registrar_.Init(profile_->GetPrefs());
-
-  pref_change_registrar_.Add(
-      policies_.access_mode,
-      base::BindRepeating(&ExternalPrintersPrefBridge::AccessModeUpdated,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
-      policies_.blacklist,
-      base::BindRepeating(&ExternalPrintersPrefBridge::BlacklistUpdated,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
-      policies_.whitelist,
-      base::BindRepeating(&ExternalPrintersPrefBridge::WhitelistUpdated,
-                          base::Unretained(this)));
-  Initialize();
-}
-
-void ExternalPrintersPrefBridge::Initialize() {
-  BlacklistUpdated();
-  WhitelistUpdated();
-  AccessModeUpdated();
-}
-
-void ExternalPrintersPrefBridge::AccessModeUpdated() {
-  const PrefService* prefs = profile_->GetPrefs();
-  ExternalPrinters::AccessMode mode = ExternalPrinters::UNSET;
-  int mode_val = prefs->GetInteger(policies_.access_mode);
-  if (mode_val >= ExternalPrinters::BLACKLIST_ONLY &&
-      mode_val <= ExternalPrinters::ALL_ACCESS) {
-    mode = static_cast<ExternalPrinters::AccessMode>(mode_val);
-  } else {
-    LOG(ERROR) << "Unrecognized access mode";
-    return;
-  }
-
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetAccessMode(mode);
-}
-
-void ExternalPrintersPrefBridge::BlacklistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetBlacklist(
-        FromPrefs(profile_->GetPrefs(), policies_.blacklist));
-}
-
-void ExternalPrintersPrefBridge::WhitelistUpdated() {
-  base::WeakPtr<ExternalPrinters> printers =
-      ExternalPrintersFactory::Get()->GetForProfile(profile_);
-  if (printers)
-    printers->SetWhitelist(
-        FromPrefs(profile_->GetPrefs(), policies_.whitelist));
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/printing/external_printers_pref_bridge.h b/chrome/browser/chromeos/printing/external_printers_pref_bridge.h
deleted file mode 100644
index b7041109..0000000
--- a/chrome/browser/chromeos/printing/external_printers_pref_bridge.h
+++ /dev/null
@@ -1,55 +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_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
-#define CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
-
-#include <string>
-
-#include "base/macros.h"
-#include "chrome/browser/chromeos/printing/external_printers_policies.h"
-#include "components/prefs/pref_change_registrar.h"
-
-class Profile;
-
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
-
-namespace chromeos {
-
-class ExternalPrinters;
-
-// Observe preference changes and propogate changes to ExternalPrinters.
-class ExternalPrintersPrefBridge {
- public:
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry,
-                                   const ExternalPrinterPolicies& policies);
-
-  ExternalPrintersPrefBridge(const ExternalPrinterPolicies& policies,
-                             Profile* profile);
-
- private:
-  // Retrieve initial values for preferences.
-  void Initialize();
-
-  // Handle update for the access mode policy.
-  void AccessModeUpdated();
-
-  // Handle updates for the blacklist policy.
-  void BlacklistUpdated();
-
-  // Handle updates for the whitelist policy.
-  void WhitelistUpdated();
-
-  Profile* profile_;
-  const ExternalPrinterPolicies policies_;
-  PrefChangeRegistrar pref_change_registrar_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExternalPrintersPrefBridge);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_PRINTING_EXTERNAL_PRINTERS_PREF_BRIDGE_H_
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager.cc b/chrome/browser/chromeos/printing/synced_printers_manager.cc
index ee03322..5a3886b 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/synced_printers_manager.cc
@@ -10,22 +10,14 @@
 #include <utility>
 #include <vector>
 
-#include "base/bind.h"
-#include "base/feature_list.h"
 #include "base/guid.h"
-#include "base/json/json_reader.h"
-#include "base/md5.h"
 #include "base/observer_list_threadsafe.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/device_external_printers_settings_bridge.h"
-#include "chrome/browser/chromeos/printing/external_printers.h"
-#include "chrome/browser/chromeos/printing/external_printers_factory.h"
-#include "chrome/browser/chromeos/printing/external_printers_pref_bridge.h"
+#include "chrome/browser/chromeos/printing/enterprise_printers_provider.h"
 #include "chrome/browser/chromeos/printing/printer_configurer.h"
 #include "chrome/browser/chromeos/printing/printers_sync_bridge.h"
 #include "chrome/browser/chromeos/printing/specifics_translation.h"
@@ -43,36 +35,9 @@
 
 namespace {
 
-// Returns the collection policies for user printers.
-ExternalPrinterPolicies UserPolicyNames() {
-  ExternalPrinterPolicies user_policy_names;
-  user_policy_names.access_mode = prefs::kRecommendedNativePrintersAccessMode;
-  user_policy_names.blacklist = prefs::kRecommendedNativePrintersBlacklist;
-  user_policy_names.whitelist = prefs::kRecommendedNativePrintersWhitelist;
-  return user_policy_names;
-}
-
-// Returns the collection policies for device printers.
-ExternalPrinterPolicies DevicePolicyNames() {
-  ExternalPrinterPolicies device_policy_names;
-  device_policy_names.access_mode = kDeviceNativePrintersAccessMode;
-  device_policy_names.blacklist = kDeviceNativePrintersBlacklist;
-  device_policy_names.whitelist = kDeviceNativePrintersWhitelist;
-  return device_policy_names;
-}
-
-// Inserts |printer| into |new_printers| if the id does not already exist.
-// Returns true if the insert was successful, false if there was a conflict.
-bool InsertIfNotPresent(std::unordered_map<std::string, Printer>* new_printers,
-                        const Printer& printer) {
-  std::pair<std::unordered_map<std::string, Printer>::iterator, bool> ret =
-      new_printers->insert({printer.id(), printer});
-  return ret.second;
-}
-
 class SyncedPrintersManagerImpl : public SyncedPrintersManager,
                                   public PrintersSyncBridge::Observer,
-                                  public ExternalPrinters::Observer {
+                                  public EnterprisePrintersProvider::Observer {
  public:
   SyncedPrintersManagerImpl(Profile* profile,
                             std::unique_ptr<PrintersSyncBridge> sync_bridge)
@@ -81,42 +46,14 @@
         observers_(new base::ObserverListThreadSafe<
                    SyncedPrintersManager::Observer>()),
         weak_factory_(this) {
-    pref_change_registrar_.Init(profile->GetPrefs());
-    pref_change_registrar_.Add(
-        prefs::kRecommendedNativePrinters,
-        base::Bind(&SyncedPrintersManagerImpl::UpdateRecommendedPrinters,
-                   base::Unretained(this)));
-    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      user_external_printers_observer_ =
-          std::make_unique<ExternalPrintersPrefBridge>(UserPolicyNames(),
-                                                       profile_);
-      user_external_printers_ =
-          ExternalPrintersFactory::Get()->GetForProfile(profile_);
-      if (user_external_printers_) {
-        user_external_printers_->AddObserver(this);
-      }
-    }
-
-    device_external_printers_observer_ =
-        std::make_unique<DeviceExternalPrintersSettingsBridge>(
-            DevicePolicyNames(), CrosSettings::Get());
-    device_external_printers_ =
-        DeviceExternalPrintersFactory::Get()->GetForDevice();
-    if (device_external_printers_) {
-      device_external_printers_->AddObserver(this);
-    }
-
-    UpdateRecommendedPrinters();
+    printers_provider_ =
+        EnterprisePrintersProvider::Create(CrosSettings::Get(), profile_);
+    printers_provider_->AddObserver(this);
     sync_bridge_->AddObserver(this);
   }
 
   ~SyncedPrintersManagerImpl() override {
-    if (user_external_printers_) {
-      user_external_printers_->RemoveObserver(this);
-    }
-    if (device_external_printers_) {
-      device_external_printers_->RemoveObserver(this);
-    }
+    printers_provider_->RemoveObserver(this);
     sync_bridge_->RemoveObserver(this);
   }
 
@@ -132,9 +69,10 @@
     return printers;
   }
 
-  std::vector<Printer> GetEnterprisePrinters() const override {
+  bool GetEnterprisePrinters(std::vector<Printer>& printers) const override {
     base::AutoLock l(lock_);
-    return GetEnterprisePrintersLocked();
+    printers = GetEnterprisePrintersLocked();
+    return enterprise_printers_are_ready_;
   }
 
   std::unique_ptr<Printer> GetPrinter(
@@ -190,14 +128,18 @@
         GetConfiguredPrinters());
   }
 
-  // ExternalPrinters::Observer override
+  // EnterprisePrintersProvider::Observer override
   void OnPrintersChanged(
-      bool valid,
-      const std::map<const ::std::string, const Printer>& printers) override {
-    // User or device policy printers changed.  Update the lists.
-    // |valid| is safe to ignore here since we're recomputing and the cached
-    // printers are always cleared.
-    UpdateRecommendedPrinters();
+      bool complete,
+      const std::unordered_map<std::string, Printer>& printers) override {
+    // Enterprise printers policy changed.  Update the lists.
+    base::AutoLock l(lock_);
+    enterprise_printers_ = printers;
+    enterprise_printers_are_ready_ = complete;
+    observers_->Notify(
+        FROM_HERE,
+        &SyncedPrintersManager::Observer::OnEnterprisePrintersChanged,
+        GetEnterprisePrintersLocked(), enterprise_printers_are_ready_);
   }
 
  private:
@@ -237,128 +179,12 @@
     sync_bridge_->UpdatePrinter(PrinterToSpecifics(printer));
   }
 
-  // Reads printers provided by NativePrinters policy.  Appends ids to |new_ids|
-  // in the order they were received. Appends printers to |new_printers| indexed
-  // by id.  Discards printers with duplicate ids.
-  void PolicyNativePrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    const PrefService* prefs = profile_->GetPrefs();
-    const base::ListValue* values =
-        prefs->GetList(prefs::kRecommendedNativePrinters);
-    for (const auto& value : *values) {
-      std::string printer_json;
-      if (!value.GetAsString(&printer_json)) {
-        NOTREACHED();
-        continue;
-      }
-
-      std::unique_ptr<base::DictionaryValue> printer_dictionary =
-          base::DictionaryValue::From(base::JSONReader::ReadDeprecated(
-              printer_json, base::JSON_ALLOW_TRAILING_COMMAS));
-
-      if (!printer_dictionary) {
-        LOG(WARNING) << "Ignoring invalid printer.  Invalid JSON object: "
-                     << printer_json;
-        continue;
-      }
-
-      // Policy printers don't have id's but the ids only need to be locally
-      // unique so we'll hash the record.  This will not collide with the
-      // UUIDs generated for user entries.
-      std::string id = base::MD5String(printer_json);
-      printer_dictionary->SetString(kPrinterId, id);
-
-      auto new_printer = RecommendedPrinterToPrinter(*printer_dictionary);
-      if (!new_printer) {
-        LOG(WARNING) << "Recommended printer is malformed.";
-        continue;
-      }
-
-      if (!InsertIfNotPresent(new_printers, *new_printer)) {
-        // Printer is already in the list.
-        LOG(WARNING) << "Duplicate printer ignored: " << id;
-        continue;
-      }
-
-      new_ids->push_back(id);
-    }
-  }
-
-  // Reads printers provided by NativePrintersBulkConfigurations and
-  // DeviceNativePrinters policies. Appends ids to |new_ids| in the order they
-  // were received. Appends printers to |new_printers| indexed by id. Discards
-  // printers with duplicate ids.
-  void ReadPolicyPrinters(
-      base::WeakPtr<ExternalPrinters> external_printers,
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    DCHECK(new_ids);
-    DCHECK(new_printers);
-
-    if (!external_printers || !external_printers->IsPolicySet())
-      return;
-
-    const std::map<const std::string, const Printer>& printers =
-        external_printers->GetPrinters();
-    for (const auto& entry : printers) {
-      Printer printer(entry.second);
-      printer.set_source(Printer::SRC_POLICY);
-
-      if (!InsertIfNotPresent(new_printers, printer)) {
-        // Printer is already in the list.
-        LOG(WARNING) << "Duplicate printer ignored: " << printer.id();
-        continue;
-      }
-
-      new_ids->push_back(printer.id());
-    }
-  }
-
-  // Reads printers provided by NativePrintersBulkConfigurations policy.
-  void BulkPolicyPrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    ReadPolicyPrinters(ExternalPrintersFactory::Get()->GetForProfile(profile_),
-                       new_ids, new_printers);
-  }
-
-  // Reads printers provided by DeviceNativePrinters policy.
-  void DevicePolicyPrinters(
-      std::vector<std::string>* new_ids,
-      std::unordered_map<std::string, Printer>* new_printers) {
-    ReadPolicyPrinters(DeviceExternalPrintersFactory::Get()->GetForDevice(),
-                       new_ids, new_printers);
-  }
-
-  void UpdateRecommendedPrinters() {
-    // Parse the policy JSON into new structures outside the lock.
-    std::vector<std::string> new_ids;
-    std::unordered_map<std::string, Printer> new_printers;
-
-    PolicyNativePrinters(&new_ids, &new_printers);
-    if (base::FeatureList::IsEnabled(features::kBulkPrinters)) {
-      BulkPolicyPrinters(&new_ids, &new_printers);
-    }
-    DevicePolicyPrinters(&new_ids, &new_printers);
-
-    // Objects not in the most recent update get deallocated after method
-    // exit.
-    base::AutoLock l(lock_);
-    enterprise_printer_ids_.swap(new_ids);
-    enterprise_printers_.swap(new_printers);
-    observers_->Notify(
-        FROM_HERE,
-        &SyncedPrintersManager::Observer::OnEnterprisePrintersChanged,
-        GetEnterprisePrintersLocked());
-  }
-
   std::vector<Printer> GetEnterprisePrintersLocked() const {
     lock_.AssertAcquired();
     std::vector<Printer> ret;
     ret.reserve(enterprise_printers_.size());
-    for (const std::string& id : enterprise_printer_ids_) {
-      ret.push_back(enterprise_printers_.find(id)->second);
+    for (auto kv : enterprise_printers_) {
+      ret.push_back(kv.second);
     }
     return ret;
   }
@@ -366,29 +192,17 @@
   mutable base::Lock lock_;
 
   Profile* profile_;
-  PrefChangeRegistrar pref_change_registrar_;
-
-  // Bulk user printers. Unowned.
-  base::WeakPtr<ExternalPrinters> user_external_printers_;
-
-  // Device printers. Unowned.
-  base::WeakPtr<ExternalPrinters> device_external_printers_;
 
   // The backend for profile printers.
   std::unique_ptr<PrintersSyncBridge> sync_bridge_;
 
-  // Connects external printers preferences with the tracking object.
-  std::unique_ptr<ExternalPrintersPrefBridge> user_external_printers_observer_;
+  // The Object that provides updates about enterprise printers.
+  std::unique_ptr<EnterprisePrintersProvider> printers_provider_;
 
-  // Connects external printers device settings with the tracking object.
-  std::unique_ptr<DeviceExternalPrintersSettingsBridge>
-      device_external_printers_observer_;
-
-  // Enterprise printers as of the last time we got a policy update.  The ids
-  // vector is used to preserve the received ordering.
-  std::vector<std::string> enterprise_printer_ids_;
-  // Map is from id to printer.
+  // Enterprise printers as of the last time we got a policy update.
   std::unordered_map<std::string, Printer> enterprise_printers_;
+  // This flag is set to true if all enterprise policies were loaded.
+  bool enterprise_printers_are_ready_ = false;
 
   // Map of printer ids to PrinterConfigurer setup fingerprints at the time
   // the printers was last installed with CUPS.
@@ -404,9 +218,7 @@
 // static
 void SyncedPrintersManager::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterListPref(prefs::kRecommendedNativePrinters);
-
-  ExternalPrintersPrefBridge::RegisterProfilePrefs(registry, UserPolicyNames());
+  EnterprisePrintersProvider::RegisterProfilePrefs(registry);
 }
 
 // static
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager.h b/chrome/browser/chromeos/printing/synced_printers_manager.h
index 2ec83c2..dd73d19 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager.h
+++ b/chrome/browser/chromeos/printing/synced_printers_manager.h
@@ -39,7 +39,8 @@
     virtual void OnConfiguredPrintersChanged(
         const std::vector<Printer>& printers) = 0;
     virtual void OnEnterprisePrintersChanged(
-        const std::vector<Printer>& printers) = 0;
+        const std::vector<Printer>& printers,
+        bool enterprise_printers_are_ready) {}
   };
 
   static std::unique_ptr<SyncedPrintersManager> Create(
@@ -53,8 +54,9 @@
   // Returns the printers that are saved in preferences.
   virtual std::vector<Printer> GetConfiguredPrinters() const = 0;
 
-  // Returns printers from enterprise policy.
-  virtual std::vector<Printer> GetEnterprisePrinters() const = 0;
+  // Replaces given vector with vector of printers from enterprise policy.
+  // Returns true if the enterprise policy was loaded and is valid.
+  virtual bool GetEnterprisePrinters(std::vector<Printer>& printers) const = 0;
 
   // Returns the printer with id |printer_id|, or nullptr if no such printer
   // exists.  Searches both Configured and Enterprise printers.
diff --git a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
index ff90a8c..5191943d 100644
--- a/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/synced_printers_manager_unittest.cc
@@ -72,7 +72,8 @@
   }
 
   void OnEnterprisePrintersChanged(
-      const std::vector<Printer>& printer) override {
+      const std::vector<Printer>& printer,
+      bool enterprise_printers_are_ready) override {
     enterprise_printers_ = printer;
   }
 
@@ -200,10 +201,12 @@
   // TestingPrefSyncableService assumes ownership of |value|.
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
-  auto printers = manager_->GetEnterprisePrinters();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(printers);
   ASSERT_EQ(2U, printers.size());
-  EXPECT_EQ("Color Laser", printers[0].display_name());
-  EXPECT_EQ("ipp://192.168.1.5", printers[1].uri());
+  // order not specified
+  // EXPECT_EQ("Color Laser", printers[0].display_name());
+  // EXPECT_EQ("ipp://192.168.1.5", printers[1].uri());
   EXPECT_EQ(Printer::Source::SRC_POLICY, printers[1].source());
 }
 
@@ -217,7 +220,8 @@
   // TestingPrefSyncableService assumes ownership of |value|.
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
-  auto printers = manager_->GetEnterprisePrinters();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(printers);
 
   const Printer& from_list = printers.front();
   std::unique_ptr<Printer> retrieved = manager_->GetPrinter(from_list.id());
@@ -250,7 +254,9 @@
   prefs->SetManagedPref(prefs::kRecommendedNativePrinters, std::move(value));
 
   // Figure out the id of the enterprise printer that was just installed.
-  std::string enterprise_id = manager_->GetEnterprisePrinters().at(0).id();
+  std::vector<Printer> printers;
+  manager_->GetEnterprisePrinters(printers);
+  std::string enterprise_id = printers.at(0).id();
 
   Printer configured(kTestPrinterId);
 
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 14f8a645..f0d7fd6 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -288,6 +288,29 @@
       const char* expected_content_regular_window,
       const char* exptected_content_incognito_window);
 
+  void InstallRequestHeaderModifyingExtension() {
+    TestExtensionDir test_dir;
+    test_dir.WriteManifest(R"({
+        "name": "Web Request Header Modifying Extension",
+        "manifest_version": 2,
+        "version": "0.1",
+        "background": { "scripts": ["background.js"] },
+        "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"]
+      })");
+    test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
+        chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
+          details.requestHeaders.push({name: 'foo', value: 'bar'});
+          return {requestHeaders: details.requestHeaders};
+        }, {urls: ['*://*/echoheader*']}, ['blocking', 'requestHeaders']);
+
+        chrome.test.sendMessage('ready');
+      )");
+
+    ExtensionTestMessageListener listener("ready", false);
+    ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
+    EXPECT_TRUE(listener.WaitUntilSatisfied());
+  }
+
   // Ensures requests made by the |worker_script_name| service worker can be
   // intercepted by extensions.
   void RunServiceWorkerFetchTest(const std::string& worker_script_name);
@@ -320,6 +343,18 @@
     test_dirs_.push_back(std::move(dir));
   }
 
+  void RegisterServiceWorker(const std::string& worker_path,
+                             const base::Optional<std::string>& scope) {
+    GURL url = embedded_test_server()->GetURL(
+        "/service_worker/create_service_worker.html");
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+    std::string script = content::JsReplace("register($1, $2);", worker_path,
+                                            scope ? *scope : std::string());
+    EXPECT_EQ(
+        "DONE",
+        EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), script));
+  }
+
  private:
   std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_;
 };
@@ -752,39 +787,21 @@
   embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  TestExtensionDir test_dir;
-  test_dir.WriteManifest(R"({
-        "name": "Web Request Service Worker Test",
-        "manifest_version": 2,
-        "version": "0.1",
-        "background": { "scripts": ["background.js"] },
-        "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"]
-      })");
-  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
-        chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
-          details.requestHeaders.push({name: 'foo', value: 'bar'});
-          return {requestHeaders: details.requestHeaders};
-        }, {urls: ['*://*/echoheader*']}, ['blocking', 'requestHeaders']);
+  // Install the test extension.
+  InstallRequestHeaderModifyingExtension();
 
-        chrome.test.sendMessage('ready');
-      )");
-
-  ExtensionTestMessageListener listener("ready", false);
-  ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
-  EXPECT_TRUE(listener.WaitUntilSatisfied());
-
-  // Register the |worker_script_name| as a service worker.
-  EXPECT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL(
-                     "/service_worker/create_service_worker.html")));
-  EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
-                           "register('" + worker_script_name + "');"));
-
+  // Register a service worker and navigate to a page it controls.
+  RegisterServiceWorker(worker_script_name, base::nullopt);
   EXPECT_TRUE(ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL("/service_worker/fetch_from_page.html")));
-  // Ensure the extension was able to intercept the service worker request and
-  // modify the request headers.
+
+  // Make a fetch from the controlled page. Depending on the worker script, the
+  // fetch might go to the service worker and be re-issued, or might fallback to
+  // network, or skip the worker, etc. In any case, this function expects a
+  // network request to happen, and that the extension modify the headers of the
+  // request before it goes to network. Verify that it was able to inject a
+  // header of "foo=bar".
   EXPECT_EQ("bar", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
                           "fetch_from_page('/echoheader?foo');"));
 }
@@ -2527,6 +2544,43 @@
   RunServiceWorkerFetchTest("empty.js");
 }
 
+// Ensure that extensions can intercept service worker navigation preload
+// requests.
+IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
+                       ServiceWorkerNavigationPreload) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Install the test extension.
+  InstallRequestHeaderModifyingExtension();
+
+  // Register a service worker that uses navigation preload.
+  RegisterServiceWorker("/service_worker/navigation_preload_worker.js",
+                        "/echoheader");
+
+  // Navigate to "/echoheader". The browser will detect that the service worker
+  // above is registered with this scope and has navigation preload enabled.
+  // So it will send the navigation preload request to network while at the same
+  // time starting up the service worker. The service worker will get the
+  // response for the navigation preload request, and respond with it to create
+  // the page.
+  GURL url = embedded_test_server()->GetURL(
+      "/echoheader?foo&service-worker-navigation-preload");
+  ui_test_utils::NavigateToURL(browser(), url);
+
+  // Since the request was to "/echoheader", the response describes the request
+  // headers.
+  //
+  // The extension is expected to add a "foo: bar" header to the request
+  // before it goes to network. Verify that it did.
+  //
+  // The browser adds a "service-worker-navigation-preload: true" header for
+  // navigation preload requests, so also sanity check that header to prove
+  // that this test is really testing the navigation preload request.
+  EXPECT_EQ("bar\ntrue",
+            EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                   "document.body.textContent;"));
+}
+
 // Ensure we don't strip off initiator incorrectly in web request events when
 // both the normal and incognito contexts are active. Regression test for
 // crbug.com/934398.
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.cc b/chrome/browser/extensions/bookmark_app_extension_util.cc
index a6c6151..dbb6d114 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.cc
+++ b/chrome/browser/extensions/bookmark_app_extension_util.cc
@@ -78,15 +78,21 @@
 #endif  // defined(OS_CHROMEOS)
 }
 
+bool CanBookmarkAppReparentTab(bool shortcut_created) {
+#if defined(OS_MACOSX)
+  // On macOS it is only possible to reparent the window when the shortcut (app
+  // shim) was created.  See https://crbug.com/915571.
+  return shortcut_created;
+#else
+  return true;
+#endif
+}
+
 void BookmarkAppReparentTab(content::WebContents* contents,
                             const Extension* extension) {
-#if !defined(OS_MACOSX)
   // Reparent the tab into an app window immediately when opening as a window.
-  // TODO(https://crbug.com/915571): Reparent the tab on Mac just like the
-  // other platforms.
   if (base::FeatureList::IsEnabled(::features::kDesktopPWAWindowing))
     ReparentWebContentsIntoAppBrowser(contents, extension);
-#endif  // !defined(OS_MACOSX)
 }
 
 bool CanBookmarkAppRevealAppShim() {
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.h b/chrome/browser/extensions/bookmark_app_extension_util.h
index c2811b9a..f332c08c 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.h
+++ b/chrome/browser/extensions/bookmark_app_extension_util.h
@@ -26,6 +26,7 @@
 bool CanBookmarkAppBePinnedToShelf();
 void BookmarkAppPinToShelf(const Extension* extension);
 
+bool CanBookmarkAppReparentTab(bool shortcut_created);
 void BookmarkAppReparentTab(content::WebContents* contents,
                             const Extension* extension);
 
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc
index 5db05cb..bdab6320 100644
--- a/chrome/browser/extensions/bookmark_app_helper.cc
+++ b/chrome/browser/extensions/bookmark_app_helper.cc
@@ -50,6 +50,7 @@
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/browser/pref_names.h"
@@ -462,8 +463,28 @@
 
   web_app::RecordAppBanner(contents_, web_app_info_.app_url);
 
-  if (create_shortcuts_ && CanBookmarkAppCreateOsShortcuts())
-    BookmarkAppCreateOsShortcuts(profile_, extension, base::DoNothing());
+  if (create_shortcuts_ && CanBookmarkAppCreateOsShortcuts()) {
+    BookmarkAppCreateOsShortcuts(
+        profile_, extension,
+        base::BindOnce(&BookmarkAppHelper::OnShortcutCreationCompleted,
+                       weak_factory_.GetWeakPtr(), extension->id()));
+  } else {
+    OnShortcutCreationCompleted(extension->id(), false /* shortcuts_created */);
+  }
+}
+
+void BookmarkAppHelper::OnShortcutCreationCompleted(
+    const std::string& extension_id,
+    bool shortcut_created) {
+  // Note that the extension may have been deleted since this task was posted,
+  // so use |extension_id| as a weak pointer.
+  const Extension* extension =
+      ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(
+          extension_id);
+  if (!extension) {
+    callback_.Run(nullptr, web_app_info_);
+    return;
+  }
 
   if (create_shortcuts_ && CanBookmarkAppBePinnedToShelf())
     BookmarkAppPinToShelf(extension);
@@ -474,7 +495,7 @@
       (chrome::FindBrowserWithWebContents(contents_) != nullptr);
   // TODO(loyso): Reparenting must be implemented in
   // chrome/browser/ui/web_applications/ UI layer as a post-install step.
-  if (reparent_tab) {
+  if (reparent_tab && CanBookmarkAppReparentTab(shortcut_created)) {
     DCHECK(!profile_->IsOffTheRecord());
     BookmarkAppReparentTab(contents_, extension);
     if (CanBookmarkAppRevealAppShim())
diff --git a/chrome/browser/extensions/bookmark_app_helper.h b/chrome/browser/extensions/bookmark_app_helper.h
index 3ad37a61..2805806 100644
--- a/chrome/browser/extensions/bookmark_app_helper.h
+++ b/chrome/browser/extensions/bookmark_app_helper.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <set>
+#include <string>
 #include <vector>
 
 #include "base/callback.h"
@@ -153,6 +154,10 @@
   // installation steps.
   void FinishInstallation(const Extension* extension);
 
+  // Called when shortcut creation is complete.
+  void OnShortcutCreationCompleted(const std::string& extension_id,
+                                   bool shortcut_created);
+
   // Overridden from content::NotificationObserver:
   void Observe(int type,
                const content::NotificationSource& source,
diff --git a/chrome/browser/extensions/bookmark_app_helper_browsertest.cc b/chrome/browser/extensions/bookmark_app_helper_browsertest.cc
index 2c92f41..dfa01cb 100644
--- a/chrome/browser/extensions/bookmark_app_helper_browsertest.cc
+++ b/chrome/browser/extensions/bookmark_app_helper_browsertest.cc
@@ -95,6 +95,9 @@
     // ~WebAppReadyMsgWatcher() is called on the IO thread, but
     // |bookmark_app_helper_| must be destroyed on the UI thread.
     bookmark_app_helper_.reset();
+    // Web contents reparenting happens only after shortcut creation has
+    // completed.
+    quit_closure_.Run();
   }
 
   void Wait() {
@@ -109,7 +112,6 @@
                             bool is_update) override {
     if (!expected_app_title_.empty())
       EXPECT_EQ(expected_app_title_, extension->name());
-    quit_closure_.Run();
   }
 
   // DialogBrowserTest:
@@ -184,12 +186,9 @@
                                           bookmark_app_helper_->web_app_info_);
   Wait();  // Quits when the extension install completes.
 
-#if !defined(OS_MACOSX)
-  // We do not reparent the tab on OS X.
   Browser* app_browser = chrome::FindBrowserWithWebContents(web_contents());
   EXPECT_TRUE(app_browser->is_app());
   EXPECT_NE(app_browser, browser());
-#endif  // defined(OS_MACOSX)
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index 2111eaa..fe4b44e 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -40,6 +40,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 
 namespace extensions {
@@ -76,19 +77,22 @@
                         base::CompareCase::SENSITIVE))
     return nullptr;
 
-  size_t query_string_pos = request.relative_url.find('?');
-  std::string header_name =
-      request.relative_url.substr(query_string_pos + 1);
+  std::string content;
+  net::test_server::RequestQuery headers =
+      net::test_server::ParseQuery(request.GetURL());
+  for (const auto& header : headers) {
+    std::string header_name = header.first;
+    std::string header_value;
+    if (request.headers.find(header_name) != request.headers.end())
+      header_value = request.headers.at(header_name);
+    if (!content.empty())
+      content += "\n";
+    content += header_value;
+  }
 
-  std::string header_value;
-  auto it = request.headers.find(header_name);
-  if (it != request.headers.end())
-    header_value = it->second;
-
-  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
-      new net::test_server::BasicHttpResponse);
+  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
   http_response->set_code(net::HTTP_OK);
-  http_response->set_content(header_value);
+  http_response->set_content(content);
   return std::move(http_response);
 }
 
@@ -146,6 +150,7 @@
 
 }  // namespace
 
+// TODO(karandeepb): See if this custom handling can be removed.
 ExtensionApiTest::ExtensionApiTest() {
   embedded_test_server()->RegisterRequestHandler(
       base::Bind(&HandleServerRedirectRequest));
diff --git a/chrome/browser/extensions/options_page_apitest.cc b/chrome/browser/extensions/options_page_apitest.cc
index 422465d..b6c9180 100644
--- a/chrome/browser/extensions/options_page_apitest.cc
+++ b/chrome/browser/extensions/options_page_apitest.cc
@@ -51,7 +51,7 @@
   content::RenderFrameHost* frame = content::FrameMatchingPredicate(
       tab_strip->GetActiveWebContents(),
       base::Bind(&content::FrameHasSourceUrl,
-                 GURL(chrome::kChromeUIExtensionsFrameURL)));
+                 GURL(chrome::kChromeUIExtensionsURL)));
   EXPECT_TRUE(content::ExecuteScript(
       frame,
       kScriptClickOptionButton));
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 91925b7..8a0d4f3 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -309,6 +309,13 @@
       << message_;
 }
 
+// Tests chrome.tabs APIs.
+IN_PROC_BROWSER_TEST_P(ServiceWorkerBasedBackgroundTest, TabsBasic) {
+  ASSERT_TRUE(
+      RunExtensionTest("service_worker/worker_based_background/tabs_basic"))
+      << message_;
+}
+
 // Tests chrome.tabs events.
 IN_PROC_BROWSER_TEST_P(ServiceWorkerBasedBackgroundTest, TabsEvents) {
   ASSERT_TRUE(
diff --git a/chrome/browser/first_run/first_run_internal_posix_browsertest.cc b/chrome/browser/first_run/first_run_internal_posix_browsertest.cc
index 34563ae..9ed432a7fa 100644
--- a/chrome/browser/first_run/first_run_internal_posix_browsertest.cc
+++ b/chrome/browser/first_run/first_run_internal_posix_browsertest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/test/base/in_process_browser_test.h"
-
 #include <signal.h>
 
 #include "base/bind.h"
@@ -65,7 +63,7 @@
     // Send a signal to myself. This should post a task for the next run loop
     // iteration to set browser_shutdown::IsTryingToQuit(), and interrupt the
     // RunLoop.
-    raise(SIGTERM);
+    raise(SIGINT);
     inspected_state_ = true;
   }
 
@@ -77,7 +75,7 @@
 // Test the first run flow for showing the modal dialog that surfaces the first
 // run dialog. Ensure browser startup safely handles a signal while the modal
 // RunLoop is running.
-IN_PROC_BROWSER_TEST_F(FirstRunInternalPosixTest, HandleSigterm) {
+IN_PROC_BROWSER_TEST_F(FirstRunInternalPosixTest, HandleSigint) {
   // Never reached. PreMainMessageLoopRunImpl() should return before this task
   // is run.
   ADD_FAILURE() << "Should never be called";
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 315e9000..51c8515 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -833,6 +833,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-autofill-local-card-migration-uses-strike-system-v2",
+    "owners": [ "annelim@google.com", "jsaul@google.com" , "jiahuiguo@google.com"],
+    "expiry_milestone": 76
+  },
+  {
     "name": "enable-autofill-manual-fallback",
     // "owners": [ "your-team" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b5ff356..37bac18 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -425,6 +425,14 @@
     "after the user clicks the save button. Once migration is finished, "
     "the dialog will be updated with the results of each card.";
 
+const char kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Name[] =
+    "Enable limit on offering to migrate local cards repeatedly using the "
+    "updated strike system implementation";
+const char kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Description[] =
+    "If enabled, uses the updated strike system implementation to prevent "
+    "offering prompts for local card migration if it has repeatedly been "
+    "ignored, declined, or failed.";
+
 const char kEnableAutofillNativeDropdownViewsName[] =
     "Display Autofill Dropdown Using Views";
 const char kEnableAutofillNativeDropdownViewsDescription[] =
@@ -457,11 +465,11 @@
     "it has repeatedly been ignored, declined, or failed.";
 
 const char kEnableAutofillSaveCreditCardUsesStrikeSystemV2Name[] =
-    "Enable limit on offering to save the same credit card repeatedly using the"
-    "updated strike system implementation";
+    "Enable limit on offering to save the same credit card repeatedly using "
+    "the updated strike system implementation";
 const char kEnableAutofillSaveCreditCardUsesStrikeSystemV2Description[] =
-    "If enabled, uses the updated strike system implementation to prevent"
-    "popping up the credit card offer-to-save prompt if it has repeatedly been"
+    "If enabled, uses the updated strike system implementation to prevent "
+    "popping up the credit card offer-to-save prompt if it has repeatedly been "
     "ignored, declined, or failed.";
 
 const char kEnableAutofillSendExperimentIdsInPaymentsRPCsName[] =
@@ -1078,10 +1086,6 @@
     "Don't permit an iframe to navigate the top level browsing context unless "
     "they are same-origin or the iframe is processing a user gesture.";
 
-const char kGamepadVibrationName[] = "Gamepad Vibration";
-const char kGamepadVibrationDescription[] =
-    "Enables haptic vibration effects on supported gamepads.";
-
 const char kGpuRasterizationName[] = "GPU rasterization";
 const char kGpuRasterizationDescription[] =
     "Use GPU to rasterize web content. Requires impl-side painting.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 158487e..d3ce0e5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -284,6 +284,10 @@
 extern const char kEnableAutofillLocalCardMigrationShowFeedbackName[];
 extern const char kEnableAutofillLocalCardMigrationShowFeedbackDescription[];
 
+extern const char kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Name[];
+extern const char
+    kEnableAutofillLocalCardMigrationUsesStrikeSystemV2Description[];
+
 extern const char kEnableAutofillSaveCardImprovedUserConsentName[];
 extern const char kEnableAutofillSaveCardImprovedUserConsentDescription[];
 
@@ -641,9 +645,6 @@
 extern const char kFramebustingName[];
 extern const char kFramebustingDescription[];
 
-extern const char kGamepadVibrationName[];
-extern const char kGamepadVibrationDescription[];
-
 extern const char kGpuRasterizationName[];
 extern const char kGpuRasterizationDescription[];
 extern const char kForceGpuRasterization[];
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index 2be21cc..9565fb1 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/data_use_measurement/chrome_data_use_ascriber.h"
 #include "chrome/browser/net/chrome_network_delegate.h"
-#include "chrome/browser/net/dns_probe_service.h"
 #include "chrome/browser/net/failing_url_request_interceptor.h"
 #include "chrome/browser/net/proxy_service_factory.h"
 #include "chrome/common/chrome_content_client.h"
@@ -284,9 +283,6 @@
   globals_->data_use_ascriber =
       std::make_unique<data_use_measurement::ChromeDataUseAscriber>();
 
-  globals_->dns_probe_service =
-      std::make_unique<chrome_browser_net::DnsProbeService>();
-
   if (command_line.HasSwitch(network::switches::kIgnoreUrlFetcherCertRequests))
     net::URLFetcher::SetIgnoreCertificateRequests(true);
 
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index 0835488..062d1ab 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -36,10 +36,6 @@
 class PrefService;
 class SystemNetworkContextManager;
 
-namespace chrome_browser_net {
-class DnsProbeService;
-}
-
 namespace data_use_measurement {
 class ChromeDataUseAscriber;
 }
@@ -117,10 +113,6 @@
     scoped_refptr<extensions::EventRouterForwarder>
         extension_event_router_forwarder;
 #endif
-    // NetErrorTabHelper uses |dns_probe_service| to send DNS probes when a
-    // main frame load fails with a DNS error in order to provide more useful
-    // information to the renderer so it can show a more specific error page.
-    std::unique_ptr<chrome_browser_net::DnsProbeService> dns_probe_service;
   };
 
   // |net_log| must either outlive the IOThread or be NULL.
diff --git a/chrome/browser/net/dns_probe_browsertest.cc b/chrome/browser/net/dns_probe_browsertest.cc
index a65c1e8a..a16abd3c 100644
--- a/chrome/browser/net/dns_probe_browsertest.cc
+++ b/chrome/browser/net/dns_probe_browsertest.cc
@@ -10,8 +10,10 @@
 #include "base/run_loop.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/time/default_tick_clock.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/io_thread.h"
+#include "chrome/browser/net/dns_probe_service_factory.h"
 #include "chrome/browser/net/dns_probe_test_util.h"
 #include "chrome/browser/net/net_error_tab_helper.h"
 #include "chrome/browser/net/url_request_mock_util.h"
@@ -33,10 +35,11 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "net/base/net_errors.h"
-#include "net/dns/dns_test_util.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/url_request/url_request_failed_job.h"
 #include "services/network/public/cpp/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 using base::Bind;
 using base::BindOnce;
@@ -49,7 +52,6 @@
 using content::WebContents;
 using error_page::DnsProbeStatus;
 using google_util::LinkDoctorBaseURL;
-using net::MockDnsClientRule;
 using net::URLRequestFailedJob;
 using ui_test_utils::NavigateToURL;
 using ui_test_utils::NavigateToURLBlockUntilNavigationsComplete;
@@ -65,37 +67,56 @@
   base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closure);
 }
 
-// Wraps DnsProbeService and delays callbacks until someone calls
-// CallDelayedCallbacks.  This allows the DnsProbeBrowserTest to enforce a
+// Wraps DnsProbeService and delays probes until someone calls
+// StartDelayedProbes.  This allows the DnsProbeBrowserTest to enforce a
 // stricter ordering of events.
 class DelayingDnsProbeService : public DnsProbeService {
  public:
-  DelayingDnsProbeService() {}
+  DelayingDnsProbeService(
+      const DnsProbeServiceFactory::NetworkContextGetter&
+          network_context_getter,
+      const DnsProbeServiceFactory::DnsConfigChangeManagerGetter&
+          dns_config_change_manager_getter)
+      : dns_probe_service_impl_(DnsProbeServiceFactory::CreateForTesting(
+            network_context_getter,
+            dns_config_change_manager_getter,
+            base::DefaultTickClock::GetInstance())) {}
 
   ~DelayingDnsProbeService() override { EXPECT_TRUE(delayed_probes_.empty()); }
 
-  void ProbeDns(const ProbeCallback& callback) override {
-    delayed_probes_.push_back(callback);
+  static std::unique_ptr<KeyedService> Create(
+      const DnsProbeServiceFactory::NetworkContextGetter&
+          network_context_getter,
+      const DnsProbeServiceFactory::DnsConfigChangeManagerGetter&
+          dns_config_change_manager_getter,
+      content::BrowserContext* context) {
+    return std::make_unique<DelayingDnsProbeService>(
+        network_context_getter, dns_config_change_manager_getter);
+  }
+
+  void ProbeDns(ProbeCallback callback) override {
+    delayed_probes_.push_back(std::move(callback));
   }
 
   void StartDelayedProbes() {
-    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
     std::vector<ProbeCallback> probes;
     probes.swap(delayed_probes_);
 
-    for (std::vector<ProbeCallback>::const_iterator i = probes.begin();
+    for (std::vector<ProbeCallback>::iterator i = probes.begin();
          i != probes.end(); ++i) {
-      DnsProbeService::ProbeDns(*i);
+      dns_probe_service_impl_->ProbeDns(std::move(*i));
     }
   }
 
   int delayed_probe_count() const {
-    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     return delayed_probes_.size();
   }
 
  private:
+  std::unique_ptr<DnsProbeService> dns_probe_service_impl_;
   std::vector<ProbeCallback> delayed_probes_;
 };
 
@@ -252,77 +273,32 @@
 
 class DnsProbeBrowserTestIOThreadHelper {
  public:
-  DnsProbeBrowserTestIOThreadHelper();
-
-  void SetUpOnIOThread(IOThread* io_thread);
+  void SetUpOnIOThread();
   void CleanUpOnIOThreadAndDeleteHelper();
 
-  void SetMockDnsClientRules(MockDnsClientRule::ResultType system_good_result,
-                             MockDnsClientRule::ResultType public_good_result);
   void SetCorrectionServiceNetError(int net_error);
   void SetCorrectionServiceDelayRequests(bool delay_requests);
   void SetRequestDestructionCallback(const base::Closure& callback);
-  void StartDelayedProbes(int expected_delayed_probe_count);
   void InterceptURLLoaderRequest(
       content::URLLoaderInterceptor::RequestParams* params);
 
  private:
-  IOThread* io_thread_;
-  DnsProbeService* original_dns_probe_service_;
-  DelayingDnsProbeService* delaying_dns_probe_service_;
-  BreakableCorrectionInterceptor* interceptor_;
+  std::unique_ptr<BreakableCorrectionInterceptor> interceptor_;
 };
 
-DnsProbeBrowserTestIOThreadHelper::DnsProbeBrowserTestIOThreadHelper()
-    : io_thread_(NULL),
-      original_dns_probe_service_(NULL),
-      delaying_dns_probe_service_(NULL),
-      interceptor_(NULL) {}
-
-void DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread(IOThread* io_thread) {
+void DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread() {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
-  CHECK(io_thread);
-  CHECK(!io_thread_);
-  CHECK(!original_dns_probe_service_);
-  CHECK(!delaying_dns_probe_service_);
   CHECK(!interceptor_);
 
-  io_thread_ = io_thread;
-
-  delaying_dns_probe_service_ = new DelayingDnsProbeService();
-
-  IOThread::Globals* globals = io_thread_->globals();
-  original_dns_probe_service_ = globals->dns_probe_service.release();
-  globals->dns_probe_service.reset(delaying_dns_probe_service_);
-
-  interceptor_ = new BreakableCorrectionInterceptor;
+  interceptor_ = std::make_unique<BreakableCorrectionInterceptor>();
 }
 
 void DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper() {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
 
-  IOThread::Globals* globals = io_thread_->globals();
-  std::unique_ptr<DnsProbeService> delaying_dns_probe_service(
-      globals->dns_probe_service.release());
-  globals->dns_probe_service.reset(original_dns_probe_service_);
-
-  CHECK_EQ(delaying_dns_probe_service_, delaying_dns_probe_service.get());
-
   delete this;
 }
 
-void DnsProbeBrowserTestIOThreadHelper::SetMockDnsClientRules(
-    MockDnsClientRule::ResultType system_result,
-    MockDnsClientRule::ResultType public_result) {
-  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
-
-  DnsProbeService* service = io_thread_->globals()->dns_probe_service.get();
-  service->SetSystemClientForTesting(
-      CreateMockDnsClientForProbes(system_result));
-  service->SetPublicClientForTesting(
-      CreateMockDnsClientForProbes(public_result));
-}
-
 void DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceNetError(
     int net_error) {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
@@ -344,19 +320,6 @@
   interceptor_->SetRequestDestructionCallback(callback);
 }
 
-void DnsProbeBrowserTestIOThreadHelper::StartDelayedProbes(
-    int expected_delayed_probe_count) {
-  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
-
-  CHECK(delaying_dns_probe_service_);
-
-  int actual_delayed_probe_count =
-      delaying_dns_probe_service_->delayed_probe_count();
-  EXPECT_EQ(expected_delayed_probe_count, actual_delayed_probe_count);
-
-  delaying_dns_probe_service_->StartDelayedProbes();
-}
-
 void DnsProbeBrowserTestIOThreadHelper::InterceptURLLoaderRequest(
     content::URLLoaderInterceptor::RequestParams* params) {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
@@ -380,11 +343,19 @@
   // DnsProbeStatus messages of its currently active tab monitored.
   void SetActiveBrowser(Browser* browser);
 
+  // Sets the results the FakeHostResolver will return for the system and
+  // public DnsProbeRunners.  Since this mocks out the
+  // NetworkContext & HostResolver used by the DnsProbeService it doesn't really
+  // give an end-to-end test, but content::TestHostResolver mocks don't affect
+  // the probes since they use HostResolverSource::DNS, so this is the best
+  // that can be done currently.
+  void SetFakeHostResolverResults(
+      std::vector<FakeHostResolver::SingleResult> system_results,
+      std::vector<FakeHostResolver::SingleResult> public_results);
+
   void SetCorrectionServiceBroken(bool broken);
   void SetCorrectionServiceDelayRequests(bool delay_requests);
   void WaitForDelayedRequestDestruction();
-  void SetMockDnsClientRules(MockDnsClientRule::ResultType system_result,
-                             MockDnsClientRule::ResultType public_result);
 
   // These functions are often used to wait for two navigations because two
   // pages are loaded when navigation corrections are enabled: a blank page, so
@@ -413,14 +384,23 @@
  private:
   void OnDnsProbeStatusSent(DnsProbeStatus dns_probe_status);
 
+  network::mojom::NetworkContext* GetNetworkContext() {
+    return network_context_.get();
+  }
+
+  network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager();
+
+  std::unique_ptr<FakeHostResolverNetworkContext> network_context_;
+  std::unique_ptr<FakeDnsConfigChangeManager> dns_config_change_manager_;
   DnsProbeBrowserTestIOThreadHelper* helper_;
+  DelayingDnsProbeService* delaying_dns_probe_service_;
 
   // Browser that methods apply to.
   Browser* active_browser_;
   // Helper that current has its DnsProbeStatus messages monitored.
   NetErrorTabHelper* monitored_tab_helper_;
 
-  bool awaiting_dns_probe_status_;
+  std::unique_ptr<base::RunLoop> awaiting_dns_probe_status_run_loop_;
   // Queue of statuses received but not yet consumed by WaitForSentStatus().
   std::list<DnsProbeStatus> dns_probe_status_queue_;
 
@@ -430,8 +410,7 @@
 DnsProbeBrowserTest::DnsProbeBrowserTest()
     : helper_(new DnsProbeBrowserTestIOThreadHelper()),
       active_browser_(NULL),
-      monitored_tab_helper_(NULL),
-      awaiting_dns_probe_status_(false) {}
+      monitored_tab_helper_(NULL) {}
 
 DnsProbeBrowserTest::~DnsProbeBrowserTest() {
   // No tests should have any unconsumed probe statuses.
@@ -447,7 +426,7 @@
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       BindOnce(&DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread,
-               Unretained(helper_), g_browser_process->io_thread()));
+               Unretained(helper_)));
 
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -490,6 +469,16 @@
 }
 
 void DnsProbeBrowserTest::SetActiveBrowser(Browser* browser) {
+  delaying_dns_probe_service_ = static_cast<DelayingDnsProbeService*>(
+      DnsProbeServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+          browser->profile(),
+          base::BindRepeating(
+              &DelayingDnsProbeService::Create,
+              base::BindRepeating(&DnsProbeBrowserTest::GetNetworkContext,
+                                  base::Unretained(this)),
+              base::BindRepeating(
+                  &DnsProbeBrowserTest::GetDnsConfigChangeManager,
+                  base::Unretained(this)))));
   // If currently watching a NetErrorTabHelper, stop doing so before start
   // watching another.
   if (monitored_tab_helper_) {
@@ -503,6 +492,15 @@
       Bind(&DnsProbeBrowserTest::OnDnsProbeStatusSent, Unretained(this)));
 }
 
+void DnsProbeBrowserTest::SetFakeHostResolverResults(
+    std::vector<FakeHostResolver::SingleResult> system_results,
+    std::vector<FakeHostResolver::SingleResult> public_results) {
+  ASSERT_FALSE(network_context_);
+
+  network_context_ = std::make_unique<FakeHostResolverNetworkContext>(
+      std::move(system_results), std::move(public_results));
+}
+
 void DnsProbeBrowserTest::SetCorrectionServiceBroken(bool broken) {
   int net_error = broken ? net::ERR_NAME_NOT_RESOLVED : net::OK;
 
@@ -546,28 +544,22 @@
       num_navigations);
 }
 
-void DnsProbeBrowserTest::SetMockDnsClientRules(
-    MockDnsClientRule::ResultType system_result,
-    MockDnsClientRule::ResultType public_result) {
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      BindOnce(&DnsProbeBrowserTestIOThreadHelper::SetMockDnsClientRules,
-               Unretained(helper_), system_result, public_result));
-}
-
 void DnsProbeBrowserTest::StartDelayedProbes(int expected_delayed_probe_count) {
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      BindOnce(&DnsProbeBrowserTestIOThreadHelper::StartDelayedProbes,
-               Unretained(helper_), expected_delayed_probe_count));
+  ASSERT_TRUE(delaying_dns_probe_service_);
+
+  int actual_delayed_probe_count =
+      delaying_dns_probe_service_->delayed_probe_count();
+  EXPECT_EQ(expected_delayed_probe_count, actual_delayed_probe_count);
+
+  delaying_dns_probe_service_->StartDelayedProbes();
 }
 
 DnsProbeStatus DnsProbeBrowserTest::WaitForSentStatus() {
-  CHECK(!awaiting_dns_probe_status_);
+  CHECK(!awaiting_dns_probe_status_run_loop_);
   while (dns_probe_status_queue_.empty()) {
-    awaiting_dns_probe_status_ = true;
-    base::RunLoop().Run();
-    awaiting_dns_probe_status_ = false;
+    awaiting_dns_probe_status_run_loop_ = std::make_unique<base::RunLoop>();
+    awaiting_dns_probe_status_run_loop_->Run();
+    awaiting_dns_probe_status_run_loop_ = nullptr;
   }
 
   CHECK(!dns_probe_status_queue_.empty());
@@ -623,12 +615,52 @@
 void DnsProbeBrowserTest::OnDnsProbeStatusSent(
     DnsProbeStatus dns_probe_status) {
   dns_probe_status_queue_.push_back(dns_probe_status);
-  if (awaiting_dns_probe_status_)
-    base::RunLoop::QuitCurrentWhenIdleDeprecated();
+  if (awaiting_dns_probe_status_run_loop_)
+    awaiting_dns_probe_status_run_loop_->Quit();
 }
 
+network::mojom::DnsConfigChangeManagerPtr
+DnsProbeBrowserTest::GetDnsConfigChangeManager() {
+  network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+  dns_config_change_manager_ = std::make_unique<FakeDnsConfigChangeManager>(
+      mojo::MakeRequest(&dns_config_change_manager_ptr));
+  return dns_config_change_manager_ptr;
+}
+
+// Test Fixture for tests where the DNS probes should succeed.
+class DnsProbeSuccessfulProbesTest : public DnsProbeBrowserTest {
+  void SetUpOnMainThread() override {
+    SetFakeHostResolverResults(
+        {{net::OK, FakeHostResolver::kOneAddressResponse}},
+        {{net::OK, FakeHostResolver::kOneAddressResponse}});
+    DnsProbeBrowserTest::SetUpOnMainThread();
+  }
+};
+
+// Test Fixture for tests where the DNS probes should not resolve.
+class DnsProbeFailingProbesTest : public DnsProbeBrowserTest {
+  void SetUpOnMainThread() override {
+    SetFakeHostResolverResults(
+        {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+        {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+    DnsProbeBrowserTest::SetUpOnMainThread();
+  }
+};
+
+// Test Fixture for tests where the DNS probes should fail to connect to a DNS
+// server (timeout or unreachable host).
+class DnsProbeUnreachableProbesTest : public DnsProbeBrowserTest {
+  void SetUpOnMainThread() override {
+    SetFakeHostResolverResults(
+        {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+        {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+    DnsProbeBrowserTest::SetUpOnMainThread();
+  }
+};
+
 // Make sure probes don't break non-DNS error pages when corrections load.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithCorrectionsSuccess) {
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+                       OtherErrorWithCorrectionsSuccess) {
   SetCorrectionServiceBroken(false);
 
   NavigateToOtherError(2);
@@ -637,7 +669,8 @@
 
 // Make sure probes don't break non-DNS error pages when corrections failed to
 // load.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithCorrectionsFailure) {
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
+                       OtherErrorWithCorrectionsFailure) {
   SetCorrectionServiceBroken(true);
 
   NavigateToOtherError(2);
@@ -645,10 +678,9 @@
 }
 
 // Make sure probes don't break DNS error pages when corrections load.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
                        NxdomainProbeResultWithWorkingCorrections) {
   SetCorrectionServiceBroken(false);
-  SetMockDnsClientRules(MockDnsClientRule::OK, MockDnsClientRule::OK);
 
   NavigateToDnsError(2);
   ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
@@ -669,11 +701,10 @@
 
 // Make sure probes don't break corrections when probes complete before the
 // corrections load.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest,
                        NxdomainProbeResultWithWorkingSlowCorrections) {
   SetCorrectionServiceBroken(false);
   SetCorrectionServiceDelayRequests(true);
-  SetMockDnsClientRules(MockDnsClientRule::OK, MockDnsClientRule::OK);
 
   NavigateToDnsError(1);
   // A blank page should be displayed while the corrections are loaded.
@@ -705,10 +736,9 @@
 }
 
 // Make sure probes update DNS error page properly when they're supposed to.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
                        NoInternetProbeResultWithBrokenCorrections) {
   SetCorrectionServiceBroken(true);
-  SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
 
   NavigateToDnsError(2);
 
@@ -731,11 +761,10 @@
 
 // Make sure probes don't break corrections when probes complete before the
 // corrections request returns an error.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
                        NoInternetProbeResultWithSlowBrokenCorrections) {
   SetCorrectionServiceBroken(true);
   SetCorrectionServiceDelayRequests(true);
-  SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
 
   NavigateToDnsError(1);
   // A blank page should be displayed while the corrections load.
@@ -766,10 +795,9 @@
 }
 
 // Double-check to make sure sync failures don't explode.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, SyncFailureWithBrokenCorrections) {
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest,
+                       SyncFailureWithBrokenCorrections) {
   SetCorrectionServiceBroken(true);
-  SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
-
   NavigateToDnsError(2);
 
   EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
@@ -794,10 +822,9 @@
 // TODO(mmenke):  Add a test for the cross process navigation case.
 // TODO(mmenke):  This test could flakily pass due to the timeout on downloading
 //                the corrections.  Disable that timeout for browser tests.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsLoadStopped) {
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest, CorrectionsLoadStopped) {
   SetCorrectionServiceDelayRequests(true);
   SetCorrectionServiceBroken(true);
-  SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
 
   NavigateToDnsError(1);
 
@@ -817,10 +844,10 @@
 
 // Test that pressing the stop button cancels the load of corrections, and
 // receiving a probe result afterwards does not swap in a DNS error page.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsLoadStoppedSlowProbe) {
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest,
+                       CorrectionsLoadStoppedSlowProbe) {
   SetCorrectionServiceDelayRequests(true);
   SetCorrectionServiceBroken(true);
-  SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
 
   NavigateToDnsError(1);
 
@@ -842,7 +869,7 @@
 }
 
 // Make sure probes don't run for subframe DNS errors.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, NoProbeInSubframe) {
+IN_PROC_BROWSER_TEST_F(DnsProbeSuccessfulProbesTest, NoProbeInSubframe) {
   SetCorrectionServiceBroken(false);
 
   NavigateToURL(browser(),
@@ -857,13 +884,12 @@
 }
 
 // Make sure browser sends NOT_RUN properly when probes are disabled.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, ProbesDisabled) {
+IN_PROC_BROWSER_TEST_F(DnsProbeUnreachableProbesTest, ProbesDisabled) {
   // Disable probes (And corrections).
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kAlternateErrorPagesEnabled, false);
 
   SetCorrectionServiceBroken(true);
-  SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
 
   NavigateToDnsError(1);
 
@@ -876,7 +902,7 @@
 
 // Test the case that corrections are disabled, but DNS probes are enabled.
 // This is the case with Chromium builds.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsDisabled) {
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest, CorrectionsDisabled) {
   // Disable corrections.
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kAlternateErrorPagesEnabled, false);
@@ -888,8 +914,6 @@
   NetErrorTabHelper::set_state_for_testing(
       NetErrorTabHelper::TESTING_FORCE_ENABLED);
 
-  SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
-
   // Just one commit and one sent status, since corrections are disabled.
   NavigateToDnsError(1);
   EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
@@ -908,7 +932,7 @@
 
 // Test incognito mode.  Corrections should be disabled, but DNS probes are
 // still enabled.
-IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, Incognito) {
+IN_PROC_BROWSER_TEST_F(DnsProbeFailingProbesTest, Incognito) {
   // Requests to the correction service should work if any are made, so the test
   // will fail if one is requested unexpectedly.
   SetCorrectionServiceBroken(false);
@@ -916,8 +940,6 @@
   Browser* incognito = CreateIncognitoBrowser();
   SetActiveBrowser(incognito);
 
-  SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
-
   // Just one commit and one sent status, since the corrections are disabled.
   NavigateToDnsError(1);
   EXPECT_EQ(error_page::DNS_PROBE_STARTED, WaitForSentStatus());
diff --git a/chrome/browser/net/dns_probe_runner.cc b/chrome/browser/net/dns_probe_runner.cc
index 77cb9ce..50ade733 100644
--- a/chrome/browser/net/dns_probe_runner.cc
+++ b/chrome/browser/net/dns_probe_runner.cc
@@ -7,30 +7,12 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/task/post_task.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "net/base/address_list.h"
-#include "net/base/ip_endpoint.h"
+#include "net/base/host_port_pair.h"
 #include "net/base/net_errors.h"
-#include "net/base/network_change_notifier.h"
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_response.h"
-#include "net/dns/dns_transaction.h"
-#include "net/dns/dns_util.h"
-#include "net/dns/public/dns_protocol.h"
-#include "net/log/net_log_with_source.h"
-
-using base::TimeDelta;
-using content::BrowserThread;
-using net::AddressList;
-using net::DnsClient;
-using net::DnsResponse;
-using net::DnsTransaction;
-using net::DnsTransactionFactory;
-using net::IPEndPoint;
-using net::NetLogWithSource;
-using net::NetworkChangeNotifier;
+#include "services/network/public/mojom/network_context.mojom.h"
 
 namespace chrome_browser_net {
 
@@ -40,11 +22,14 @@
 
 DnsProbeRunner::Result EvaluateResponse(
     int net_error,
-    const DnsResponse* response) {
+    const base::Optional<net::AddressList>& resolved_addresses) {
   switch (net_error) {
     case net::OK:
       break;
 
+    case net::ERR_FAILED:
+      return DnsProbeRunner::UNKNOWN;
+
     // ERR_NAME_NOT_RESOLVED maps to NXDOMAIN, which means the server is working
     // but returned a wrong answer.
     case net::ERR_NAME_NOT_RESOLVED:
@@ -66,81 +51,88 @@
       return DnsProbeRunner::UNREACHABLE;
   }
 
-  AddressList addr_list;
-  TimeDelta ttl;
-  DnsResponse::Result result = response->ParseToAddressList(&addr_list, &ttl);
-
-  if (result != DnsResponse::DNS_PARSE_OK)
-    return DnsProbeRunner::FAILING;
-  else if (addr_list.empty())
+  if (!resolved_addresses) {
+    // If net_error is OK, resolved_addresses should be set. The binding is not
+    // closed here since it will be closed by the caller anyway.
+    mojo::ReportBadMessage("resolved_addresses not set when net_error=OK");
+    return DnsProbeRunner::UNKNOWN;
+  } else if (resolved_addresses.value().empty()) {
     return DnsProbeRunner::INCORRECT;
-  else
+  } else {
     return DnsProbeRunner::CORRECT;
+  }
 }
 
 }  // namespace
 
-DnsProbeRunner::DnsProbeRunner() : result_(UNKNOWN), weak_factory_(this) {}
-
-DnsProbeRunner::~DnsProbeRunner() {}
-
-void DnsProbeRunner::SetClient(std::unique_ptr<net::DnsClient> client) {
-  client_ = std::move(client);
+DnsProbeRunner::DnsProbeRunner(
+    net::DnsConfigOverrides dns_config_overrides,
+    const NetworkContextGetter& network_context_getter)
+    : binding_(this),
+      dns_config_overrides_(dns_config_overrides),
+      network_context_getter_(network_context_getter),
+      result_(UNKNOWN) {
+  CreateHostResolver();
 }
 
-void DnsProbeRunner::RunProbe(const base::Closure& callback) {
+DnsProbeRunner::~DnsProbeRunner() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DnsProbeRunner::RunProbe(base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!callback.is_null());
-  DCHECK(client_.get());
+  DCHECK(host_resolver_);
   DCHECK(callback_.is_null());
-  DCHECK(!transaction_.get());
+  DCHECK(!binding_);
 
-  callback_ = callback;
-  DnsTransactionFactory* factory = client_->GetTransactionFactory();
-  if (!factory) {
-    // If the DnsTransactionFactory is NULL, then the DnsConfig is invalid, so
-    // the runner can't run a transaction.  Return UNKNOWN asynchronously.
-    result_ = UNKNOWN;
-    base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
-                             base::BindOnce(&DnsProbeRunner::CallCallback,
-                                            weak_factory_.GetWeakPtr()));
-    return;
-  }
+  network::mojom::ResolveHostClientPtr client_ptr;
+  binding_.Bind(mojo::MakeRequest(&client_ptr));
+  binding_.set_connection_error_handler(base::BindOnce(
+      &DnsProbeRunner::OnMojoConnectionError, base::Unretained(this)));
 
-  transaction_ = factory->CreateTransaction(
-      kKnownGoodHostname, net::dns_protocol::kTypeA,
-      base::Bind(&DnsProbeRunner::OnTransactionComplete,
-                 weak_factory_.GetWeakPtr()),
-      NetLogWithSource(), net::SecureDnsMode::AUTOMATIC);
+  network::mojom::ResolveHostParametersPtr parameters =
+      network::mojom::ResolveHostParameters::New();
+  parameters->dns_query_type = net::DnsQueryType::A;
+  parameters->source = net::HostResolverSource::DNS;
+  parameters->allow_cached_response = false;
 
-  transaction_->Start();
+  host_resolver_->ResolveHost(net::HostPortPair(kKnownGoodHostname, 80),
+                              std::move(parameters), std::move(client_ptr));
+
+  callback_ = std::move(callback);
 }
 
 bool DnsProbeRunner::IsRunning() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return !callback_.is_null();
 }
 
-void DnsProbeRunner::OnTransactionComplete(DnsTransaction* transaction,
-                                           int net_error,
-                                           const DnsResponse* response,
-                                           bool secure) {
+void DnsProbeRunner::OnComplete(
+    int32_t result,
+    const base::Optional<net::AddressList>& resolved_addresses) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!callback_.is_null());
-  DCHECK(transaction_.get());
-  DCHECK_EQ(transaction_.get(), transaction);
 
-  result_ = EvaluateResponse(net_error, response);
-  transaction_.reset();
+  result_ = EvaluateResponse(result, resolved_addresses);
+  binding_.Close();
 
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
-                           base::BindOnce(&DnsProbeRunner::CallCallback,
-                                          weak_factory_.GetWeakPtr()));
+  // ResolveHost will call OnComplete asynchronously, so callback_ can be
+  // invoked directly here.  Clear callback in case it starts a new probe
+  // immediately.
+  std::move(callback_).Run();
 }
 
-void DnsProbeRunner::CallCallback() {
-  DCHECK(!callback_.is_null());
-  DCHECK(!transaction_.get());
+void DnsProbeRunner::CreateHostResolver() {
+  host_resolver_.reset();
+  network_context_getter_.Run()->CreateHostResolver(
+      dns_config_overrides_, mojo::MakeRequest(&host_resolver_));
+}
 
-  // Clear callback in case it starts a new probe immediately.
-  std::move(callback_).Run();
+void DnsProbeRunner::OnMojoConnectionError() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CreateHostResolver();
+  OnComplete(net::ERR_FAILED, base::nullopt);
 }
 
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_runner.h b/chrome/browser/net/dns_probe_runner.h
index 399995b..ec11936 100644
--- a/chrome/browser/net/dns_probe_runner.h
+++ b/chrome/browser/net/dns_probe_runner.h
@@ -10,24 +10,30 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/network/public/cpp/resolve_host_client_base.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 
-namespace net {
-class DnsClient;
-class DnsResponse;
-class DnsTransaction;
+namespace network {
+namespace mojom {
+class NetworkContext;
 }
+}  // namespace network
 
 namespace chrome_browser_net {
 
-// Runs DNS probes using a single DnsClient and evaluates the responses.
+// Runs DNS probes using a HostResolver and evaluates the responses.
 // (Currently requests A records for google.com and expects at least one IP
 // address in the response.)
 // Used by DnsProbeService to probe the system and public DNS configurations.
-class DnsProbeRunner {
+class DnsProbeRunner : public network::ResolveHostClientBase {
  public:
   static const char kKnownGoodHostname[];
 
+  using NetworkContextGetter =
+      base::RepeatingCallback<network::mojom::NetworkContext*(void)>;
+
   // Used in histograms; add new entries at the bottom, and don't remove any.
   enum Result {
     UNKNOWN,
@@ -37,48 +43,53 @@
     UNREACHABLE  // No response received (timeout, network unreachable, etc.).
   };
 
-  DnsProbeRunner();
-  ~DnsProbeRunner();
+  // Creates a probe runner that will use |dns_config_overrides| for the dns
+  // configuration and will use |network_context_getter| to get the
+  // NetworkContext to create the HostResolver.  The |network_context_getter|
+  // may be called multiple times.
+  DnsProbeRunner(net::DnsConfigOverrides dns_config_overrides,
+                 const NetworkContextGetter& network_context_getter);
+  ~DnsProbeRunner() override;
 
-  // Sets the DnsClient that will be used for DNS probes sent by this runner.
-  // Must be called before RunProbe; can be called repeatedly, including during
-  // a probe.  It will not affect an in-flight probe, if one is running.
-  void SetClient(std::unique_ptr<net::DnsClient> client);
-
-  // Starts a probe using the client specified with SetClient, which must have
-  // been called before RunProbe.  |callback| will be called asynchronously
-  // when the result is ready, even if it is ready synchronously.  Must not
-  // be called again until the callback is called, but may be called during the
-  // callback.
-  void RunProbe(const base::Closure& callback);
+  // Starts a probe. |callback| will be called asynchronously when the result
+  // is ready, and will not be called if the DnsProbeRunner is destroyed before
+  // the probe finishes. Must not be called again until the callback is called,
+  // but may be called during the callback.
+  void RunProbe(base::OnceClosure callback);
 
   // Returns true if a probe is running.  Guaranteed to return true after
   // RunProbe returns, and false during and after the callback.
   bool IsRunning() const;
 
   // Returns the result of the last probe.
-  Result result() const { return result_; }
+  Result result() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return result_;
+  }
+
+  // network::ResolveHostClientBase impl:
+  void OnComplete(
+      int32_t result,
+      const base::Optional<net::AddressList>& resolved_addresses) override;
 
  private:
-  void OnTransactionComplete(net::DnsTransaction* transaction,
-                             int net_error,
-                             const net::DnsResponse* response,
-                             bool secure);
-  void CallCallback();
+  void CreateHostResolver();
+  void OnMojoConnectionError();
 
-  std::unique_ptr<net::DnsClient> client_;
+  mojo::Binding<network::mojom::ResolveHostClient> binding_;
+
+  net::DnsConfigOverrides dns_config_overrides_;
+  NetworkContextGetter network_context_getter_;
+
+  network::mojom::HostResolverPtr host_resolver_;
 
   // The callback passed to |RunProbe|.  Cleared right before calling the
   // callback.
-  base::Closure callback_;
-
-  // The transaction started in |RunProbe| for the DNS probe.  Reset once the
-  // results have been examined.
-  std::unique_ptr<net::DnsTransaction> transaction_;
+  base::OnceClosure callback_;
 
   Result result_;
 
-  base::WeakPtrFactory<DnsProbeRunner> weak_factory_;
+  SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(DnsProbeRunner);
 };
diff --git a/chrome/browser/net/dns_probe_runner_unittest.cc b/chrome/browser/net/dns_probe_runner_unittest.cc
index dc5ab531..cc0e6dc9 100644
--- a/chrome/browser/net/dns_probe_runner_unittest.cc
+++ b/chrome/browser/net/dns_probe_runner_unittest.cc
@@ -5,21 +5,18 @@
 #include "chrome/browser/net/dns_probe_runner.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "chrome/browser/net/dns_probe_test_util.h"
 #include "content/public/test/test_browser_thread_bundle.h"
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_config.h"
+#include "services/network/test/test_network_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::RunLoop;
 using content::TestBrowserThreadBundle;
-using net::DnsClient;
-using net::DnsConfig;
-using net::MockDnsClientRule;
 
 namespace chrome_browser_net {
 
@@ -27,12 +24,12 @@
 
 class TestDnsProbeRunnerCallback {
  public:
-  TestDnsProbeRunnerCallback()
-      : callback_(base::Bind(&TestDnsProbeRunnerCallback::OnCalled,
-                             base::Unretained(this))),
-        called_(false) {}
+  TestDnsProbeRunnerCallback() : called_(false) {}
 
-  const base::Closure& callback() const { return callback_; }
+  base::OnceClosure callback() {
+    return base::BindOnce(&TestDnsProbeRunnerCallback::OnCalled,
+                          base::Unretained(this));
+  }
   bool called() const { return called_; }
 
  private:
@@ -41,71 +38,159 @@
     called_ = true;
   }
 
-  base::Closure callback_;
   bool called_;
 };
 
+class FakeNetworkContext : public network::TestNetworkContext {
+ public:
+  explicit FakeNetworkContext(
+      std::vector<FakeHostResolver::SingleResult> result_list)
+      : result_list_(std::move(result_list)) {}
+
+  void CreateHostResolver(
+      const base::Optional<net::DnsConfigOverrides>& config_overrides,
+      network::mojom::HostResolverRequest request) override {
+    ASSERT_FALSE(resolver_);
+    resolver_ = std::make_unique<FakeHostResolver>(std::move(request),
+                                                   std::move(result_list_));
+  }
+
+ private:
+  std::unique_ptr<FakeHostResolver> resolver_;
+  std::vector<FakeHostResolver::SingleResult> result_list_;
+};
+
+class FirstHangingThenFakeResolverNetworkContext
+    : public network::TestNetworkContext {
+ public:
+  explicit FirstHangingThenFakeResolverNetworkContext(
+      std::vector<FakeHostResolver::SingleResult> result_list)
+      : result_list_(std::move(result_list)) {}
+
+  void CreateHostResolver(
+      const base::Optional<net::DnsConfigOverrides>& config_overrides,
+      network::mojom::HostResolverRequest request) override {
+    if (call_num == 0) {
+      resolver_ = std::make_unique<HangingHostResolver>(std::move(request));
+    } else {
+      resolver_ = std::make_unique<FakeHostResolver>(std::move(request),
+                                                     std::move(result_list_));
+    }
+    call_num++;
+  }
+
+  void DestroyHostResolver() { resolver_ = nullptr; }
+
+ private:
+  int call_num = 0;
+  std::unique_ptr<network::mojom::HostResolver> resolver_;
+  std::vector<FakeHostResolver::SingleResult> result_list_;
+};
+
 class DnsProbeRunnerTest : public testing::Test {
  protected:
-  void RunTest(MockDnsClientRule::ResultType query_result,
-               DnsProbeRunner::Result expected_probe_result);
+  void SetupTest(int query_result, FakeHostResolver::Response query_response);
+  void SetupTest(std::vector<FakeHostResolver::SingleResult> result_list);
+  void RunTest(DnsProbeRunner::Result expected_probe_results);
+
+  network::mojom::NetworkContext* network_context() const {
+    return network_context_.get();
+  }
 
   TestBrowserThreadBundle bundle_;
-  DnsProbeRunner runner_;
+  std::unique_ptr<network::mojom::NetworkContext> network_context_;
+  std::unique_ptr<DnsProbeRunner> runner_;
 };
 
-void DnsProbeRunnerTest::RunTest(
-    MockDnsClientRule::ResultType query_result,
-    DnsProbeRunner::Result expected_probe_result) {
-  TestDnsProbeRunnerCallback callback;
+void DnsProbeRunnerTest::SetupTest(int query_result,
+                                   FakeHostResolver::Response query_response) {
+  SetupTest({{query_result, query_response}});
+}
 
-  runner_.SetClient(CreateMockDnsClientForProbes(query_result));
-  runner_.RunProbe(callback.callback());
-  EXPECT_TRUE(runner_.IsRunning());
+void DnsProbeRunnerTest::SetupTest(
+    std::vector<FakeHostResolver::SingleResult> result_list) {
+  network_context_ =
+      std::make_unique<FakeNetworkContext>(std::move(result_list));
+  runner_ = std::make_unique<DnsProbeRunner>(
+      net::DnsConfigOverrides(),
+      base::BindRepeating(&DnsProbeRunnerTest::network_context,
+                          base::Unretained(this)));
+}
+
+void DnsProbeRunnerTest::RunTest(DnsProbeRunner::Result expected_probe_result) {
+  TestDnsProbeRunnerCallback callback;
+  runner_->RunProbe(callback.callback());
+  EXPECT_TRUE(runner_->IsRunning());
 
   RunLoop().RunUntilIdle();
-  EXPECT_FALSE(runner_.IsRunning());
+  EXPECT_FALSE(runner_->IsRunning());
   EXPECT_TRUE(callback.called());
-  EXPECT_EQ(expected_probe_result, runner_.result());
+  EXPECT_EQ(expected_probe_result, runner_->result());
 }
 
 TEST_F(DnsProbeRunnerTest, Probe_OK) {
-  RunTest(MockDnsClientRule::OK, DnsProbeRunner::CORRECT);
+  SetupTest(net::OK, FakeHostResolver::kOneAddressResponse);
+  RunTest(DnsProbeRunner::CORRECT);
 }
 
 TEST_F(DnsProbeRunnerTest, Probe_EMPTY) {
-  RunTest(MockDnsClientRule::EMPTY, DnsProbeRunner::INCORRECT);
+  SetupTest(net::OK, FakeHostResolver::kEmptyResponse);
+  RunTest(DnsProbeRunner::INCORRECT);
 }
 
 TEST_F(DnsProbeRunnerTest, Probe_TIMEOUT) {
-  RunTest(MockDnsClientRule::TIMEOUT, DnsProbeRunner::UNREACHABLE);
+  SetupTest(net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse);
+  RunTest(DnsProbeRunner::UNREACHABLE);
 }
 
-TEST_F(DnsProbeRunnerTest, Probe_FAIL) {
-  RunTest(MockDnsClientRule::FAIL, DnsProbeRunner::INCORRECT);
+TEST_F(DnsProbeRunnerTest, Probe_NXDOMAIN) {
+  SetupTest(net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse);
+  RunTest(DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_FAILING) {
+  SetupTest(net::ERR_DNS_SERVER_FAILED, FakeHostResolver::kNoResponse);
+  RunTest(DnsProbeRunner::FAILING);
 }
 
 TEST_F(DnsProbeRunnerTest, TwoProbes) {
-  RunTest(MockDnsClientRule::OK, DnsProbeRunner::CORRECT);
-  RunTest(MockDnsClientRule::EMPTY, DnsProbeRunner::INCORRECT);
+  SetupTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+             {net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+  RunTest(DnsProbeRunner::CORRECT);
+  RunTest(DnsProbeRunner::INCORRECT);
 }
 
-TEST_F(DnsProbeRunnerTest, InvalidDnsConfig) {
-  std::unique_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
-  DnsConfig empty_config;
-  dns_client->SetConfig(empty_config);
-  ASSERT_EQ(NULL, dns_client->GetTransactionFactory());
-  runner_.SetClient(std::move(dns_client));
+TEST_F(DnsProbeRunnerTest, MojoConnectionError) {
+  // Use a HostResolverGetter that returns a HangingHostResolver on the first
+  // call, and a FakeHostResolver on the second call.
+  FirstHangingThenFakeResolverNetworkContext network_context(
+      {{net::OK, FakeHostResolver::kOneAddressResponse}});
+  runner_ = std::make_unique<DnsProbeRunner>(
+      net::DnsConfigOverrides(),
+      base::BindRepeating(
+          [](network::mojom::NetworkContext* context) { return context; },
+          base::Unretained(&network_context)));
 
   TestDnsProbeRunnerCallback callback;
-
-  runner_.RunProbe(callback.callback());
-  EXPECT_TRUE(runner_.IsRunning());
-
+  runner_->RunProbe(callback.callback());
+  EXPECT_TRUE(runner_->IsRunning());
   RunLoop().RunUntilIdle();
-  EXPECT_FALSE(runner_.IsRunning());
+  EXPECT_TRUE(runner_->IsRunning());
+  EXPECT_FALSE(callback.called());
+  // Destroy the HangingHostResolver while runner_ is still waiting for a
+  // response. The set_connection_error_handler callback should be invoked.
+  network_context.DestroyHostResolver();
+  RunLoop().RunUntilIdle();
+  // That should cause the RunProbe callback to be called with an UNKNOWN
+  // status since the HostResolver request never returned.
+  EXPECT_FALSE(runner_->IsRunning());
   EXPECT_TRUE(callback.called());
-  EXPECT_EQ(DnsProbeRunner::UNKNOWN, runner_.result());
+  EXPECT_EQ(DnsProbeRunner::UNKNOWN, runner_->result());
+
+  // Try another probe. The DnsProbeRunner should call the HostResolverGetter
+  // again, this time getting a FakeHostResolver that will successfully return
+  // an OK result.
+  RunTest(DnsProbeRunner::CORRECT);
 }
 
 }  // namespace
diff --git a/chrome/browser/net/dns_probe_service.cc b/chrome/browser/net/dns_probe_service.cc
deleted file mode 100644
index 22a2c410..0000000
--- a/chrome/browser/net/dns_probe_service.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright (c) 2012 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/net/dns_probe_service.h"
-
-#include <stdint.h>
-
-#include <utility>
-
-#include "base/metrics/field_trial.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/strings/string_number_conversions.h"
-#include "net/base/ip_address.h"
-#include "net/base/ip_endpoint.h"
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_config.h"
-#include "net/dns/public/dns_protocol.h"
-
-using base::FieldTrialList;
-using base::StringToInt;
-using error_page::DnsProbeStatus;
-using net::DnsClient;
-using net::DnsConfig;
-using net::NetworkChangeNotifier;
-
-namespace chrome_browser_net {
-
-namespace {
-
-// How long the DnsProbeService will cache the probe result for.
-// If it's older than this and we get a probe request, the service expires it
-// and starts a new probe.
-const int kMaxResultAgeMs = 5000;
-
-// The public DNS servers used by the DnsProbeService to verify internet
-// connectivity.
-const uint8_t kGooglePublicDns1[] = {8, 8, 8, 8};
-const uint8_t kGooglePublicDns2[] = {8, 8, 4, 4};
-
-DnsProbeStatus EvaluateResults(DnsProbeRunner::Result system_result,
-                               DnsProbeRunner::Result public_result) {
-  // If the system DNS is working, assume the domain doesn't exist.
-  if (system_result == DnsProbeRunner::CORRECT)
-    return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
-
-  // If the system DNS is unknown (e.g. on Android), but the public server is
-  // reachable, assume the domain doesn't exist.
-  if (system_result == DnsProbeRunner::UNKNOWN &&
-      public_result == DnsProbeRunner::CORRECT) {
-    return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
-  }
-
-  // If the system DNS is not working but another public server is, assume the
-  // DNS config is bad (or perhaps the DNS servers are down or broken).
-  if (public_result == DnsProbeRunner::CORRECT)
-    return error_page::DNS_PROBE_FINISHED_BAD_CONFIG;
-
-  // If the system DNS is not working and another public server is unreachable,
-  // assume the internet connection is down (note that system DNS may be a
-  // router on the LAN, so it may be reachable but returning errors.)
-  if (public_result == DnsProbeRunner::UNREACHABLE)
-    return error_page::DNS_PROBE_FINISHED_NO_INTERNET;
-
-  // Otherwise: the system DNS is not working and another public server is
-  // responding but with errors or incorrect results.  This is an awkward case;
-  // an invasive captive portal or a restrictive firewall may be intercepting
-  // or rewriting DNS traffic, or the public server may itself be failing or
-  // down.
-  return error_page::DNS_PROBE_FINISHED_INCONCLUSIVE;
-}
-
-void HistogramProbe(DnsProbeStatus status, base::TimeDelta elapsed) {
-  DCHECK(error_page::DnsProbeStatusIsFinished(status));
-
-  UMA_HISTOGRAM_ENUMERATION("DnsProbe.ProbeResult", status,
-                            error_page::DNS_PROBE_MAX);
-  UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.ProbeDuration", elapsed);
-}
-
-}  // namespace
-
-DnsProbeService::DnsProbeService()
-    : state_(STATE_NO_RESULT) {
-  NetworkChangeNotifier::AddDNSObserver(this);
-  SetSystemClientToCurrentConfig();
-  SetPublicClientToGooglePublicDns();
-}
-
-DnsProbeService::~DnsProbeService() {
-  NetworkChangeNotifier::RemoveDNSObserver(this);
-}
-
-void DnsProbeService::ProbeDns(const DnsProbeService::ProbeCallback& callback) {
-  pending_callbacks_.push_back(callback);
-
-  if (CachedResultIsExpired())
-    ClearCachedResult();
-
-  switch (state_) {
-    case STATE_NO_RESULT:
-      StartProbes();
-      break;
-    case STATE_RESULT_CACHED:
-      CallCallbacks();
-      break;
-    case STATE_PROBE_RUNNING:
-      // Do nothing; probe is already running, and will call the callback.
-      break;
-  }
-}
-
-void DnsProbeService::OnDNSChanged() {
-  ClearCachedResult();
-  SetSystemClientToCurrentConfig();
-}
-
-void DnsProbeService::OnInitialDNSConfigRead() {
-  OnDNSChanged();
-}
-
-void DnsProbeService::SetSystemClientForTesting(
-    std::unique_ptr<DnsClient> system_client) {
-  system_runner_.SetClient(std::move(system_client));
-}
-
-void DnsProbeService::SetPublicClientForTesting(
-    std::unique_ptr<DnsClient> public_client) {
-  public_runner_.SetClient(std::move(public_client));
-}
-
-void DnsProbeService::ClearCachedResultForTesting() {
-  ClearCachedResult();
-}
-
-void DnsProbeService::SetSystemClientToCurrentConfig() {
-  DnsConfig system_config;
-  NetworkChangeNotifier::GetDnsConfig(&system_config);
-  system_config.search.clear();
-  system_config.attempts = 1;
-  system_config.randomize_ports = false;
-
-  std::unique_ptr<DnsClient> system_client(DnsClient::CreateClient(NULL));
-  system_client->SetConfig(system_config);
-
-  system_runner_.SetClient(std::move(system_client));
-}
-
-void DnsProbeService::SetPublicClientToGooglePublicDns() {
-  DnsConfig public_config;
-  public_config.nameservers.push_back(net::IPEndPoint(
-      net::IPAddress(kGooglePublicDns1), net::dns_protocol::kDefaultPort));
-  public_config.nameservers.push_back(net::IPEndPoint(
-      net::IPAddress(kGooglePublicDns2), net::dns_protocol::kDefaultPort));
-  public_config.attempts = 1;
-  public_config.randomize_ports = false;
-
-  std::unique_ptr<DnsClient> public_client(DnsClient::CreateClient(NULL));
-  public_client->SetConfig(public_config);
-
-  public_runner_.SetClient(std::move(public_client));
-}
-
-void DnsProbeService::StartProbes() {
-  DCHECK_EQ(STATE_NO_RESULT, state_);
-
-  DCHECK(!system_runner_.IsRunning());
-  DCHECK(!public_runner_.IsRunning());
-
-  const base::Closure callback = base::Bind(&DnsProbeService::OnProbeComplete,
-                                            base::Unretained(this));
-  system_runner_.RunProbe(callback);
-  public_runner_.RunProbe(callback);
-  probe_start_time_ = base::Time::Now();
-  state_ = STATE_PROBE_RUNNING;
-
-  DCHECK(system_runner_.IsRunning());
-  DCHECK(public_runner_.IsRunning());
-}
-
-void DnsProbeService::OnProbeComplete() {
-  DCHECK_EQ(STATE_PROBE_RUNNING, state_);
-
-  if (system_runner_.IsRunning() || public_runner_.IsRunning())
-    return;
-
-  cached_result_ = EvaluateResults(system_runner_.result(),
-                                   public_runner_.result());
-  state_ = STATE_RESULT_CACHED;
-
-  HistogramProbe(cached_result_, base::Time::Now() - probe_start_time_);
-
-  CallCallbacks();
-}
-
-void DnsProbeService::CallCallbacks() {
-  DCHECK_EQ(STATE_RESULT_CACHED, state_);
-  DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
-  DCHECK(!pending_callbacks_.empty());
-
-  std::vector<ProbeCallback> callbacks;
-  callbacks.swap(pending_callbacks_);
-
-  for (std::vector<ProbeCallback>::const_iterator i = callbacks.begin();
-       i != callbacks.end(); ++i) {
-    i->Run(cached_result_);
-  }
-}
-
-void DnsProbeService::ClearCachedResult() {
-  if (state_ == STATE_RESULT_CACHED) {
-    state_ = STATE_NO_RESULT;
-    cached_result_ = error_page::DNS_PROBE_MAX;
-  }
-}
-
-bool DnsProbeService::CachedResultIsExpired() const {
-  if (state_ != STATE_RESULT_CACHED)
-    return false;
-
-  const base::TimeDelta kMaxResultAge =
-      base::TimeDelta::FromMilliseconds(kMaxResultAgeMs);
-  return base::Time::Now() - probe_start_time_ > kMaxResultAge;
-}
-
-}  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service.h b/chrome/browser/net/dns_probe_service.h
index 44aa2f1..90b1470 100644
--- a/chrome/browser/net/dns_probe_service.h
+++ b/chrome/browser/net/dns_probe_service.h
@@ -8,16 +8,9 @@
 #include <memory>
 #include <vector>
 
-#include "base/bind.h"
-#include "base/macros.h"
-#include "base/time/time.h"
-#include "chrome/browser/net/dns_probe_runner.h"
+#include "base/callback.h"
 #include "components/error_page/common/net_error_info.h"
-#include "net/base/network_change_notifier.h"
-
-namespace net {
-class DnsClient;
-}
+#include "components/keyed_service/core/keyed_service.h"
 
 namespace chrome_browser_net {
 
@@ -26,54 +19,17 @@
 // (perhaps from multiple tabs) and caches the results.
 //
 // Uses a single DNS attempt per config, and doesn't randomize source ports.
-class DnsProbeService : public net::NetworkChangeNotifier::DNSObserver {
+//
+// Use DnsProbeServiceFactory to get a service handle.
+class DnsProbeService : public KeyedService {
  public:
-  typedef base::Callback<void(error_page::DnsProbeStatus result)>
-      ProbeCallback;
+  using ProbeCallback =
+      base::OnceCallback<void(error_page::DnsProbeStatus result)>;
 
-  DnsProbeService();
-  ~DnsProbeService() override;
-
-  virtual void ProbeDns(const ProbeCallback& callback);
-
-  // NetworkChangeNotifier::DNSObserver implementation:
-  void OnDNSChanged() override;
-  void OnInitialDNSConfigRead() override;
-
-  void SetSystemClientForTesting(std::unique_ptr<net::DnsClient> system_client);
-  void SetPublicClientForTesting(std::unique_ptr<net::DnsClient> public_client);
-  void ClearCachedResultForTesting();
-
- private:
-  enum State {
-    STATE_NO_RESULT,
-    STATE_PROBE_RUNNING,
-    STATE_RESULT_CACHED,
-  };
-
-  void SetSystemClientToCurrentConfig();
-  void SetPublicClientToGooglePublicDns();
-
-  // Starts a probe (runs system and public probes).
-  void StartProbes();
-  void OnProbeComplete();
-  // Calls all |pending_callbacks_| with the |cached_result_|.
-  void CallCallbacks();
-  // Clears a cached probe result.
-  void ClearCachedResult();
-
-  bool CachedResultIsExpired() const;
-
-  State state_;
-  std::vector<ProbeCallback> pending_callbacks_;
-  base::Time probe_start_time_;
-  error_page::DnsProbeStatus cached_result_;
-
-  // DnsProbeRunners for the system DNS configuration and a public DNS server.
-  DnsProbeRunner system_runner_;
-  DnsProbeRunner public_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(DnsProbeService);
+  // Starts a DNS probe, or uses an existing probe result if a probe is already
+  // in progress or recently completed. |callback| will be called
+  // asynchronously with the probe result.
+  virtual void ProbeDns(ProbeCallback callback) = 0;
 };
 
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service_factory.cc b/chrome/browser/net/dns_probe_service_factory.cc
new file mode 100644
index 0000000..9fd02e2
--- /dev/null
+++ b/chrome/browser/net/dns_probe_service_factory.cc
@@ -0,0 +1,378 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/dns_probe_service_factory.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/time.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/storage_partition.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/public/dns_protocol.h"
+#include "services/network/public/mojom/network_service.mojom.h"
+
+namespace chrome_browser_net {
+
+namespace {
+
+// How long the DnsProbeService will cache the probe result for.
+// If it's older than this and we get a probe request, the service expires it
+// and starts a new probe.
+const int kMaxResultAgeMs = 5000;
+
+// The public DNS servers used by the DnsProbeService to verify internet
+// connectivity.
+const uint8_t kGooglePublicDns1[] = {8, 8, 8, 8};
+const uint8_t kGooglePublicDns2[] = {8, 8, 4, 4};
+
+error_page::DnsProbeStatus EvaluateResults(
+    DnsProbeRunner::Result system_result,
+    DnsProbeRunner::Result public_result) {
+  // If the system DNS is working, assume the domain doesn't exist.
+  if (system_result == DnsProbeRunner::CORRECT)
+    return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
+
+  // If the system DNS is unknown (e.g. on Android), but the public server is
+  // reachable, assume the domain doesn't exist.
+  if (system_result == DnsProbeRunner::UNKNOWN &&
+      public_result == DnsProbeRunner::CORRECT) {
+    return error_page::DNS_PROBE_FINISHED_NXDOMAIN;
+  }
+
+  // If the system DNS is not working but another public server is, assume the
+  // DNS config is bad (or perhaps the DNS servers are down or broken).
+  if (public_result == DnsProbeRunner::CORRECT)
+    return error_page::DNS_PROBE_FINISHED_BAD_CONFIG;
+
+  // If the system DNS is not working and another public server is unreachable,
+  // assume the internet connection is down (note that system DNS may be a
+  // router on the LAN, so it may be reachable but returning errors.)
+  if (public_result == DnsProbeRunner::UNREACHABLE)
+    return error_page::DNS_PROBE_FINISHED_NO_INTERNET;
+
+  // Otherwise: the system DNS is not working and another public server is
+  // responding but with errors or incorrect results.  This is an awkward case;
+  // an invasive captive portal or a restrictive firewall may be intercepting
+  // or rewriting DNS traffic, or the public server may itself be failing or
+  // down.
+  return error_page::DNS_PROBE_FINISHED_INCONCLUSIVE;
+}
+
+void HistogramProbe(error_page::DnsProbeStatus status,
+                    base::TimeDelta elapsed) {
+  DCHECK(error_page::DnsProbeStatusIsFinished(status));
+
+  UMA_HISTOGRAM_ENUMERATION("DnsProbe.ProbeResult", status,
+                            error_page::DNS_PROBE_MAX);
+  UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.ProbeDuration2", elapsed);
+}
+
+network::mojom::NetworkContext* GetNetworkContextForProfile(
+    content::BrowserContext* context) {
+  return content::BrowserContext::GetDefaultStoragePartition(context)
+      ->GetNetworkContext();
+}
+
+network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager() {
+  network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+  content::GetNetworkService()->GetDnsConfigChangeManager(
+      mojo::MakeRequest(&dns_config_change_manager_ptr));
+  return dns_config_change_manager_ptr;
+}
+
+net::DnsConfigOverrides SystemOverrides() {
+  net::DnsConfigOverrides overrides;
+  overrides.search = {};
+  overrides.attempts = 1;
+  overrides.randomize_ports = false;
+  return overrides;
+}
+
+net::DnsConfigOverrides PublicOverrides() {
+  net::DnsConfigOverrides overrides =
+      net::DnsConfigOverrides::CreateOverridingEverythingWithDefaults();
+  overrides.nameservers = std::vector<net::IPEndPoint>{
+      net::IPEndPoint(net::IPAddress(kGooglePublicDns1),
+                      net::dns_protocol::kDefaultPort),
+      net::IPEndPoint(net::IPAddress(kGooglePublicDns2),
+                      net::dns_protocol::kDefaultPort)};
+  overrides.attempts = 1;
+  overrides.randomize_ports = false;
+  return overrides;
+}
+
+class DnsProbeServiceImpl
+    : public DnsProbeService,
+      public network::mojom::DnsConfigChangeManagerClient {
+ public:
+  using NetworkContextGetter = DnsProbeServiceFactory::NetworkContextGetter;
+  using DnsConfigChangeManagerGetter =
+      DnsProbeServiceFactory::DnsConfigChangeManagerGetter;
+
+  explicit DnsProbeServiceImpl(content::BrowserContext* context);
+  DnsProbeServiceImpl(
+      const NetworkContextGetter& network_context_getter,
+      const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+      const base::TickClock* tick_clock);
+  ~DnsProbeServiceImpl() override;
+
+  // DnsProbeService implementation:
+  void ProbeDns(DnsProbeService::ProbeCallback callback) override;
+
+  // mojom::network::DnsConfigChangeManagerClient implementation:
+  void OnSystemDnsConfigChanged() override;
+
+ private:
+  enum State {
+    STATE_NO_RESULT,
+    STATE_PROBE_RUNNING,
+    STATE_RESULT_CACHED,
+  };
+
+  // Starts a probe (runs system and public probes).
+  void StartProbes();
+  void OnProbeComplete();
+  // Calls all |pending_callbacks_| with the |cached_result_|.
+  void CallCallbacks();
+  // Calls callback by posting a task to the same sequence. |pending_callbacks_|
+  // must have exactly one element.
+  void CallCallbackAsynchronously();
+  // Clears a cached probe result.
+  void ClearCachedResult();
+
+  bool CachedResultIsExpired() const;
+
+  void SetupDnsConfigChangeNotifications();
+  void OnDnsConfigChangeManagerConnectionError();
+
+  State state_;
+  std::vector<DnsProbeService::ProbeCallback> pending_callbacks_;
+  base::TimeTicks probe_start_time_;
+  error_page::DnsProbeStatus cached_result_;
+
+  NetworkContextGetter network_context_getter_;
+  DnsConfigChangeManagerGetter dns_config_change_manager_getter_;
+  mojo::Binding<network::mojom::DnsConfigChangeManagerClient> binding_;
+  // DnsProbeRunners for the system DNS configuration and a public DNS server.
+  DnsProbeRunner system_runner_;
+  DnsProbeRunner public_runner_;
+
+  // Time source for cache expiry.
+  const base::TickClock* tick_clock_;  // Not owned.
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(DnsProbeServiceImpl);
+};
+
+DnsProbeServiceImpl::DnsProbeServiceImpl(content::BrowserContext* context)
+    : DnsProbeServiceImpl(
+          base::BindRepeating(&GetNetworkContextForProfile, context),
+          base::BindRepeating(&GetDnsConfigChangeManager),
+          base::DefaultTickClock::GetInstance()) {}
+
+DnsProbeServiceImpl::DnsProbeServiceImpl(
+    const NetworkContextGetter& network_context_getter,
+    const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+    const base::TickClock* tick_clock)
+    : state_(STATE_NO_RESULT),
+      network_context_getter_(network_context_getter),
+      dns_config_change_manager_getter_(dns_config_change_manager_getter),
+      binding_(this),
+      system_runner_(SystemOverrides(), network_context_getter),
+      public_runner_(PublicOverrides(), network_context_getter),
+      tick_clock_(tick_clock) {
+  SetupDnsConfigChangeNotifications();
+}
+
+DnsProbeServiceImpl::~DnsProbeServiceImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void DnsProbeServiceImpl::ProbeDns(ProbeCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  pending_callbacks_.push_back(std::move(callback));
+
+  if (CachedResultIsExpired())
+    ClearCachedResult();
+
+  switch (state_) {
+    case STATE_NO_RESULT:
+      StartProbes();
+      break;
+    case STATE_RESULT_CACHED:
+      CallCallbackAsynchronously();
+      break;
+    case STATE_PROBE_RUNNING:
+      // Do nothing; probe is already running, and will call the callback.
+      break;
+  }
+}
+
+void DnsProbeServiceImpl::OnSystemDnsConfigChanged() {
+  ClearCachedResult();
+}
+
+void DnsProbeServiceImpl::StartProbes() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(STATE_NO_RESULT, state_);
+
+  DCHECK(!system_runner_.IsRunning());
+  DCHECK(!public_runner_.IsRunning());
+
+  // Unretained safe because the callback will not be run if the DnsProbeRunner
+  // is destroyed.
+  system_runner_.RunProbe(base::BindOnce(&DnsProbeServiceImpl::OnProbeComplete,
+                                         base::Unretained(this)));
+  public_runner_.RunProbe(base::BindOnce(&DnsProbeServiceImpl::OnProbeComplete,
+                                         base::Unretained(this)));
+  probe_start_time_ = tick_clock_->NowTicks();
+  state_ = STATE_PROBE_RUNNING;
+
+  DCHECK(system_runner_.IsRunning());
+  DCHECK(public_runner_.IsRunning());
+}
+
+void DnsProbeServiceImpl::OnProbeComplete() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(STATE_PROBE_RUNNING, state_);
+  DCHECK(!system_runner_.IsRunning() || !public_runner_.IsRunning());
+
+  if (system_runner_.IsRunning() || public_runner_.IsRunning())
+    return;
+
+  cached_result_ =
+      EvaluateResults(system_runner_.result(), public_runner_.result());
+  state_ = STATE_RESULT_CACHED;
+
+  HistogramProbe(cached_result_, tick_clock_->NowTicks() - probe_start_time_);
+
+  CallCallbacks();
+}
+
+void DnsProbeServiceImpl::CallCallbacks() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(STATE_RESULT_CACHED, state_);
+  DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
+  DCHECK(!pending_callbacks_.empty());
+
+  std::vector<ProbeCallback> callbacks;
+  callbacks.swap(pending_callbacks_);
+
+  for (std::vector<ProbeCallback>::iterator i = callbacks.begin();
+       i != callbacks.end(); ++i) {
+    std::move(*i).Run(cached_result_);
+  }
+}
+
+void DnsProbeServiceImpl::CallCallbackAsynchronously() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_EQ(STATE_RESULT_CACHED, state_);
+  DCHECK(error_page::DnsProbeStatusIsFinished(cached_result_));
+  DCHECK_EQ(1U, pending_callbacks_.size());
+
+  std::vector<ProbeCallback> callbacks;
+  callbacks.swap(pending_callbacks_);
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callbacks.front()), cached_result_));
+}
+
+void DnsProbeServiceImpl::ClearCachedResult() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (state_ == STATE_RESULT_CACHED) {
+    state_ = STATE_NO_RESULT;
+    cached_result_ = error_page::DNS_PROBE_MAX;
+  }
+}
+
+bool DnsProbeServiceImpl::CachedResultIsExpired() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (state_ != STATE_RESULT_CACHED)
+    return false;
+
+  const base::TimeDelta kMaxResultAge =
+      base::TimeDelta::FromMilliseconds(kMaxResultAgeMs);
+  return tick_clock_->NowTicks() - probe_start_time_ > kMaxResultAge;
+}
+
+void DnsProbeServiceImpl::SetupDnsConfigChangeNotifications() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  network::mojom::DnsConfigChangeManagerClientPtr client_ptr;
+  binding_.Bind(mojo::MakeRequest(&client_ptr));
+  binding_.set_connection_error_handler(base::BindRepeating(
+      &DnsProbeServiceImpl::OnDnsConfigChangeManagerConnectionError,
+      base::Unretained(this)));
+  dns_config_change_manager_getter_.Run()->RequestNotifications(
+      std::move(client_ptr));
+}
+
+void DnsProbeServiceImpl::OnDnsConfigChangeManagerConnectionError() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Clear the cache, since the configuration may have changed while not
+  // getting notifications.
+  ClearCachedResult();
+
+  binding_.Close();
+
+  SetupDnsConfigChangeNotifications();
+}
+
+}  // namespace
+
+DnsProbeService* DnsProbeServiceFactory::GetForContext(
+    content::BrowserContext* browser_context) {
+  return static_cast<DnsProbeService*>(
+      GetInstance()->GetServiceForBrowserContext(browser_context,
+                                                 true /* create */));
+}
+
+DnsProbeServiceFactory* DnsProbeServiceFactory::GetInstance() {
+  return base::Singleton<DnsProbeServiceFactory>::get();
+}
+
+DnsProbeServiceFactory::DnsProbeServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "DnsProbeService",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+DnsProbeServiceFactory::~DnsProbeServiceFactory() {}
+
+KeyedService* DnsProbeServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new DnsProbeServiceImpl(context);
+}
+
+content::BrowserContext* DnsProbeServiceFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  // Create separate service for incognito profiles.
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
+
+// static
+std::unique_ptr<DnsProbeService> DnsProbeServiceFactory::CreateForTesting(
+    const NetworkContextGetter& network_context_getter,
+    const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+    const base::TickClock* tick_clock) {
+  return std::make_unique<DnsProbeServiceImpl>(
+      network_context_getter, dns_config_change_manager_getter, tick_clock);
+}
+
+}  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service_factory.h b/chrome/browser/net/dns_probe_service_factory.h
new file mode 100644
index 0000000..07fea90
--- /dev/null
+++ b/chrome/browser/net/dns_probe_service_factory.h
@@ -0,0 +1,73 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/time/tick_clock.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+
+class KeyedService;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace network {
+namespace mojom {
+class NetworkContext;
+}
+}  // namespace network
+
+namespace chrome_browser_net {
+
+class DnsProbeService;
+
+class DnsProbeServiceFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  using NetworkContextGetter =
+      base::RepeatingCallback<network::mojom::NetworkContext*(void)>;
+  using DnsConfigChangeManagerGetter =
+      base::RepeatingCallback<network::mojom::DnsConfigChangeManagerPtr(void)>;
+
+  // Returns the DnsProbeService that supports NetworkContexts for
+  // |browser_context|.
+  static DnsProbeService* GetForContext(
+      content::BrowserContext* browser_context);
+
+  // Returns the NetworkContextServiceFactory singleton.
+  static DnsProbeServiceFactory* GetInstance();
+
+  // Creates a DnsProbeService which will use the supplied
+  // |network_context_getter| and |dns_config_change_manager_getter| instead of
+  // getting them from a BrowserContext, and uses |tick_clock| for cache
+  // expiration.
+  static std::unique_ptr<DnsProbeService> CreateForTesting(
+      const NetworkContextGetter& network_context_getter,
+      const DnsConfigChangeManagerGetter& dns_config_change_manager_getter,
+      const base::TickClock* tick_clock);
+
+ private:
+  friend struct base::DefaultSingletonTraits<DnsProbeServiceFactory>;
+
+  DnsProbeServiceFactory();
+  ~DnsProbeServiceFactory() override;
+
+  // BrowserContextKeyedServiceFactory implementation:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(DnsProbeServiceFactory);
+};
+
+}  // namespace chrome_browser_net
+
+#endif  // CHROME_BROWSER_NET_DNS_PROBE_SERVICE_FACTORY_H_
diff --git a/chrome/browser/net/dns_probe_service_factory_unittest.cc b/chrome/browser/net/dns_probe_service_factory_unittest.cc
new file mode 100644
index 0000000..e6c6edd3
--- /dev/null
+++ b/chrome/browser/net/dns_probe_service_factory_unittest.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2012 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/net/dns_probe_service_factory.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
+#include "components/error_page/common/net_error_info.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::RunLoop;
+using content::TestBrowserThreadBundle;
+using error_page::DnsProbeStatus;
+
+namespace chrome_browser_net {
+
+namespace {
+
+class DnsProbeServiceTest : public testing::Test {
+ public:
+  DnsProbeServiceTest()
+      : callback_called_(false), callback_result_(error_page::DNS_PROBE_MAX) {}
+
+  void Probe() {
+    service_->ProbeDns(base::BindOnce(&DnsProbeServiceTest::ProbeCallback,
+                                      base::Unretained(this)));
+  }
+
+  void Reset() { callback_called_ = false; }
+
+ protected:
+  network::mojom::NetworkContext* GetNetworkContext() {
+    return network_context_.get();
+  }
+
+  network::mojom::DnsConfigChangeManagerPtr GetDnsConfigChangeManager() {
+    network::mojom::DnsConfigChangeManagerPtr dns_config_change_manager_ptr;
+    dns_config_change_manager_ = std::make_unique<FakeDnsConfigChangeManager>(
+        mojo::MakeRequest(&dns_config_change_manager_ptr));
+    return dns_config_change_manager_ptr;
+  }
+
+  void ConfigureTest(
+      std::vector<FakeHostResolver::SingleResult> system_results,
+      std::vector<FakeHostResolver::SingleResult> public_results) {
+    ASSERT_FALSE(network_context_);
+
+    network_context_ = std::make_unique<FakeHostResolverNetworkContext>(
+        std::move(system_results), std::move(public_results));
+
+    service_ = DnsProbeServiceFactory::CreateForTesting(
+        base::BindRepeating(&DnsProbeServiceTest::GetNetworkContext,
+                            base::Unretained(this)),
+        base::BindRepeating(&DnsProbeServiceTest::GetDnsConfigChangeManager,
+                            base::Unretained(this)),
+        &tick_clock_);
+  }
+
+  void RunTest(DnsProbeStatus expected_result) {
+    Reset();
+
+    Probe();
+    RunLoop().RunUntilIdle();
+    EXPECT_TRUE(callback_called_);
+    EXPECT_EQ(expected_result, callback_result_);
+  }
+
+  void AdvanceTime(base::TimeDelta delta) { tick_clock_.Advance(delta); }
+
+  void SimulateDnsConfigChange() {
+    dns_config_change_manager_->SimulateDnsConfigChange();
+    RunLoop().RunUntilIdle();
+  }
+
+  void DestroyDnsConfigChangeManager() { dns_config_change_manager_ = nullptr; }
+
+  bool has_dns_config_change_manager() const {
+    return !!dns_config_change_manager_;
+  }
+
+ private:
+  void ProbeCallback(DnsProbeStatus result) {
+    EXPECT_FALSE(callback_called_);
+    callback_called_ = true;
+    callback_result_ = result;
+  }
+
+  base::SimpleTestTickClock tick_clock_;
+  TestBrowserThreadBundle bundle_;
+  std::unique_ptr<FakeHostResolverNetworkContext> network_context_;
+  std::unique_ptr<FakeDnsConfigChangeManager> dns_config_change_manager_;
+  std::unique_ptr<DnsProbeService> service_;
+  bool callback_called_;
+  DnsProbeStatus callback_result_;
+};
+
+TEST_F(DnsProbeServiceTest, Probe_OK_OK) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_OK) {
+  ConfigureTest({{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_TIMEOUT) {
+  ConfigureTest({{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_OK_FAIL) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse}},
+                {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_FAIL_OK) {
+  ConfigureTest({{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
+}
+
+TEST_F(DnsProbeServiceTest, Probe_FAIL_FAIL) {
+  ConfigureTest({{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}},
+                {{net::ERR_NAME_NOT_RESOLVED, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_INCONCLUSIVE);
+}
+
+TEST_F(DnsProbeServiceTest, Cache) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+  // Advance clock, but not enough to expire the cache.
+  AdvanceTime(base::TimeDelta::FromSeconds(4));
+  // Cached NXDOMAIN result should persist, not the result from the new rules.
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+}
+
+TEST_F(DnsProbeServiceTest, Expire) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+  // Advance clock enough to trigger cache expiration.
+  AdvanceTime(base::TimeDelta::FromSeconds(6));
+  // New rules should apply, since a new probe should be run.
+  RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, DnsConfigChange) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+  // Simulate dns config change notification.
+  SimulateDnsConfigChange();
+  // New rules should apply, since a new probe should be run.
+  RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+TEST_F(DnsProbeServiceTest, MojoConnectionError) {
+  ConfigureTest({{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}},
+                {{net::OK, FakeHostResolver::kOneAddressResponse},
+                 {net::ERR_DNS_TIMED_OUT, FakeHostResolver::kNoResponse}});
+  RunTest(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
+  DestroyDnsConfigChangeManager();
+  RunLoop().RunUntilIdle();
+  // DnsProbeService should have detected the mojo connection error and
+  // automatically called the DnsConfigChangeManagerGetter again.
+  ASSERT_TRUE(has_dns_config_change_manager());
+  // New rules should apply, since recreating the DnsConfigChangeManagerClient
+  // should also clear the cache (can't tell if a config change might have
+  // happened while not getting notifications).
+  RunTest(error_page::DNS_PROBE_FINISHED_NO_INTERNET);
+}
+
+}  // namespace
+
+}  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service_unittest.cc b/chrome/browser/net/dns_probe_service_unittest.cc
deleted file mode 100644
index b632f2f..0000000
--- a/chrome/browser/net/dns_probe_service_unittest.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2012 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/net/dns_probe_service.h"
-
-#include "base/bind.h"
-#include "base/compiler_specific.h"
-#include "base/memory/weak_ptr.h"
-#include "base/run_loop.h"
-#include "chrome/browser/net/dns_probe_runner.h"
-#include "chrome/browser/net/dns_probe_test_util.h"
-#include "components/error_page/common/net_error_info.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "net/dns/dns_test_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using base::RunLoop;
-using content::TestBrowserThreadBundle;
-using error_page::DnsProbeStatus;
-using net::MockDnsClientRule;
-
-namespace chrome_browser_net {
-
-namespace {
-
-class DnsProbeServiceTest : public testing::Test {
- public:
-  DnsProbeServiceTest()
-      : callback_called_(false),
-        callback_result_(error_page::DNS_PROBE_MAX) {
-  }
-
-  void Probe() {
-    service_.ProbeDns(base::Bind(&DnsProbeServiceTest::ProbeCallback,
-                                 base::Unretained(this)));
-  }
-
-  void Reset() {
-    callback_called_ = false;
-  }
-
- protected:
-  void SetRules(MockDnsClientRule::ResultType system_query_result,
-                MockDnsClientRule::ResultType public_query_result) {
-    service_.SetSystemClientForTesting(
-        CreateMockDnsClientForProbes(system_query_result));
-    service_.SetPublicClientForTesting(
-        CreateMockDnsClientForProbes(public_query_result));
-  }
-
-  void RunTest(MockDnsClientRule::ResultType system_query_result,
-               MockDnsClientRule::ResultType public_query_result,
-               DnsProbeStatus expected_result) {
-    Reset();
-    SetRules(system_query_result, public_query_result);
-
-    Probe();
-    RunLoop().RunUntilIdle();
-    EXPECT_TRUE(callback_called_);
-    EXPECT_EQ(expected_result, callback_result_);
-  }
-
-  void ClearCachedResult() {
-    service_.ClearCachedResultForTesting();
-  }
-
- private:
-  void ProbeCallback(DnsProbeStatus result) {
-    EXPECT_FALSE(callback_called_);
-    callback_called_ = true;
-    callback_result_ = result;
-  }
-
-  TestBrowserThreadBundle bundle_;
-  DnsProbeService service_;
-  bool callback_called_;
-  DnsProbeStatus callback_result_;
-};
-
-TEST_F(DnsProbeServiceTest, Probe_OK_OK) {
-  RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
-          error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-}
-
-TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_OK) {
-  RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::OK,
-          error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
-}
-
-TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_TIMEOUT) {
-  RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
-          error_page::DNS_PROBE_FINISHED_NO_INTERNET);
-}
-
-TEST_F(DnsProbeServiceTest, Probe_OK_FAIL) {
-  RunTest(MockDnsClientRule::OK, MockDnsClientRule::FAIL,
-          error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-}
-
-TEST_F(DnsProbeServiceTest, Probe_FAIL_OK) {
-  RunTest(MockDnsClientRule::FAIL, MockDnsClientRule::OK,
-          error_page::DNS_PROBE_FINISHED_BAD_CONFIG);
-}
-
-TEST_F(DnsProbeServiceTest, Probe_FAIL_FAIL) {
-  RunTest(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL,
-          error_page::DNS_PROBE_FINISHED_INCONCLUSIVE);
-}
-
-TEST_F(DnsProbeServiceTest, Cache) {
-  RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
-          error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-  // Cached NXDOMAIN result should persist, not the result from the new rules.
-  RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
-          error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-}
-
-TEST_F(DnsProbeServiceTest, Expire) {
-  RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
-          error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-  // Pretend cache expires.
-  ClearCachedResult();
-  // New rules should apply, since a new probe should be run.
-  RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
-          error_page::DNS_PROBE_FINISHED_NO_INTERNET);
-}
-
-}  // namespace
-
-}  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_test_util.cc b/chrome/browser/net/dns_probe_test_util.cc
index f87291f..9ab8bf4e 100644
--- a/chrome/browser/net/dns_probe_test_util.cc
+++ b/chrome/browser/net/dns_probe_test_util.cc
@@ -9,31 +9,127 @@
 
 #include "chrome/browser/net/dns_probe_runner.h"
 #include "net/base/ip_address.h"
-#include "net/dns/dns_config.h"
-#include "net/dns/public/dns_protocol.h"
-
-using net::DnsClient;
-using net::DnsConfig;
-using net::MockDnsClientRule;
-using net::MockDnsClientRuleList;
 
 namespace chrome_browser_net {
 
-std::unique_ptr<DnsClient> CreateMockDnsClientForProbes(
-    MockDnsClientRule::ResultType result) {
-  DnsConfig config;
-  net::IPAddress dns_ip(192, 168, 1, 1);
-  const uint16_t kDnsPort = net::dns_protocol::kDefaultPort;
-  config.nameservers.push_back(net::IPEndPoint(dns_ip, kDnsPort));
+namespace {
 
-  const uint16_t kTypeA = net::dns_protocol::kTypeA;
-  MockDnsClientRuleList rules;
-  rules.push_back(MockDnsClientRule(DnsProbeRunner::kKnownGoodHostname, kTypeA,
-                                    MockDnsClientRule::Result(result),
-                                    false /* delay */));
+static base::Optional<net::AddressList> AddressListForResponse(
+    FakeHostResolver::Response response) {
+  base::Optional<net::AddressList> resolved_addresses;
+  switch (response) {
+    case FakeHostResolver::kNoResponse:
+      break;
+    case FakeHostResolver::kEmptyResponse:
+      resolved_addresses = net::AddressList();
+      break;
+    case FakeHostResolver::kOneAddressResponse:
+      resolved_addresses =
+          net::AddressList(net::IPEndPoint(net::IPAddress(192, 168, 1, 1), 0));
+      break;
+  }
+  return resolved_addresses;
+}
 
-  return std::unique_ptr<DnsClient>(
-      new net::MockDnsClient(config, std::move(rules)));
+}  // namespace
+
+FakeHostResolver::FakeHostResolver(
+    network::mojom::HostResolverRequest resolver_request,
+    std::vector<SingleResult> result_list)
+    : binding_(this, std::move(resolver_request)), result_list_(result_list) {}
+
+FakeHostResolver::FakeHostResolver(
+    network::mojom::HostResolverRequest resolver_request,
+    int32_t result,
+    Response response)
+    : FakeHostResolver(std::move(resolver_request),
+                       {SingleResult(result, response)}) {}
+
+FakeHostResolver::~FakeHostResolver() = default;
+
+void FakeHostResolver::ResolveHost(
+    const net::HostPortPair& host,
+    network::mojom::ResolveHostParametersPtr optional_parameters,
+    network::mojom::ResolveHostClientPtr response_client) {
+  const SingleResult& cur_result = result_list_[next_result_];
+  if (next_result_ + 1 < result_list_.size())
+    next_result_++;
+  response_client->OnComplete(cur_result.result,
+                              AddressListForResponse(cur_result.response));
+}
+
+void FakeHostResolver::MdnsListen(
+    const net::HostPortPair& host,
+    net::DnsQueryType query_type,
+    network::mojom::MdnsListenClientPtr response_client,
+    MdnsListenCallback callback) {
+  NOTREACHED();
+}
+
+HangingHostResolver::HangingHostResolver(
+    network::mojom::HostResolverRequest resolver_request)
+    : binding_(this, std::move(resolver_request)) {}
+
+HangingHostResolver::~HangingHostResolver() = default;
+
+void HangingHostResolver::ResolveHost(
+    const net::HostPortPair& host,
+    network::mojom::ResolveHostParametersPtr optional_parameters,
+    network::mojom::ResolveHostClientPtr response_client) {
+  // Intentionally do not call response_client->OnComplete, but hang onto the
+  // |response_client| since destroying that also causes the mojo
+  // set_connection_error_handler handler to be called.
+  response_client_ = std::move(response_client);
+}
+
+void HangingHostResolver::MdnsListen(
+    const net::HostPortPair& host,
+    net::DnsQueryType query_type,
+    network::mojom::MdnsListenClientPtr response_client,
+    MdnsListenCallback callback) {
+  NOTREACHED();
+}
+
+FakeHostResolverNetworkContext::FakeHostResolverNetworkContext(
+    std::vector<FakeHostResolver::SingleResult> system_result_list,
+    std::vector<FakeHostResolver::SingleResult> public_result_list)
+    : system_result_list_(std::move(system_result_list)),
+      public_result_list_(std::move(public_result_list)) {}
+
+FakeHostResolverNetworkContext::~FakeHostResolverNetworkContext() = default;
+
+void FakeHostResolverNetworkContext::CreateHostResolver(
+    const base::Optional<net::DnsConfigOverrides>& config_overrides,
+    network::mojom::HostResolverRequest request) {
+  ASSERT_TRUE(config_overrides);
+  if (!config_overrides->nameservers) {
+    if (!system_resolver_) {
+      system_resolver_ = std::make_unique<FakeHostResolver>(
+          std::move(request), system_result_list_);
+    }
+  } else {
+    if (!public_resolver_) {
+      public_resolver_ = std::make_unique<FakeHostResolver>(
+          std::move(request), public_result_list_);
+    }
+  }
+}
+
+FakeDnsConfigChangeManager::FakeDnsConfigChangeManager(
+    network::mojom::DnsConfigChangeManagerRequest request)
+    : binding_(this, std::move(request)) {}
+
+FakeDnsConfigChangeManager::~FakeDnsConfigChangeManager() = default;
+
+void FakeDnsConfigChangeManager::RequestNotifications(
+    network::mojom::DnsConfigChangeManagerClientPtr client) {
+  ASSERT_FALSE(client_);
+  client_ = std::move(client);
+}
+
+void FakeDnsConfigChangeManager::SimulateDnsConfigChange() {
+  ASSERT_TRUE(client_);
+  client_->OnSystemDnsConfigChanged();
 }
 
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_test_util.h b/chrome/browser/net/dns_probe_test_util.h
index eea22cd..a992199 100644
--- a/chrome/browser/net/dns_probe_test_util.h
+++ b/chrome/browser/net/dns_probe_test_util.h
@@ -6,16 +6,111 @@
 #define CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
 
 #include <memory>
+#include <vector>
 
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_test_util.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+#include "services/network/test/test_network_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace chrome_browser_net {
 
-// Creates a mock DNS client with a single rule for the known-good query
-// (currently google.com) that returns |result| for A queries.
-std::unique_ptr<net::DnsClient> CreateMockDnsClientForProbes(
-    net::MockDnsClientRule::ResultType result);
+class FakeHostResolver : public network::mojom::HostResolver {
+ public:
+  enum Response {
+    kNoResponse = 0,
+    kEmptyResponse = 1,
+    kOneAddressResponse = 2,
+  };
+
+  struct SingleResult {
+    SingleResult(int32_t result, Response response)
+        : result(result), response(response) {}
+    int32_t result;
+    Response response;
+  };
+
+  FakeHostResolver(network::mojom::HostResolverRequest resolver_request,
+                   std::vector<SingleResult> result_list);
+
+  FakeHostResolver(network::mojom::HostResolverRequest resolver_request,
+                   int32_t result,
+                   Response response);
+
+  ~FakeHostResolver() override;
+
+  void ResolveHost(
+      const net::HostPortPair& host,
+      network::mojom::ResolveHostParametersPtr optional_parameters,
+      network::mojom::ResolveHostClientPtr response_client) override;
+
+  void MdnsListen(const net::HostPortPair& host,
+                  net::DnsQueryType query_type,
+                  network::mojom::MdnsListenClientPtr response_client,
+                  MdnsListenCallback callback) override;
+
+ private:
+  mojo::Binding<network::mojom::HostResolver> binding_;
+  std::vector<SingleResult> result_list_;
+  size_t next_result_ = 0;
+};
+
+class HangingHostResolver : public network::mojom::HostResolver {
+ public:
+  explicit HangingHostResolver(
+      network::mojom::HostResolverRequest resolver_request);
+  ~HangingHostResolver() override;
+
+  void ResolveHost(
+      const net::HostPortPair& host,
+      network::mojom::ResolveHostParametersPtr optional_parameters,
+      network::mojom::ResolveHostClientPtr response_client) override;
+
+  void MdnsListen(const net::HostPortPair& host,
+                  net::DnsQueryType query_type,
+                  network::mojom::MdnsListenClientPtr response_client,
+                  MdnsListenCallback callback) override;
+
+ private:
+  mojo::Binding<network::mojom::HostResolver> binding_;
+  network::mojom::ResolveHostClientPtr response_client_;
+};
+
+class FakeHostResolverNetworkContext : public network::TestNetworkContext {
+ public:
+  FakeHostResolverNetworkContext(
+      std::vector<FakeHostResolver::SingleResult> system_result_list,
+      std::vector<FakeHostResolver::SingleResult> public_result_list);
+  ~FakeHostResolverNetworkContext() override;
+
+  void CreateHostResolver(
+      const base::Optional<net::DnsConfigOverrides>& config_overrides,
+      network::mojom::HostResolverRequest request) override;
+
+ private:
+  std::vector<FakeHostResolver::SingleResult> system_result_list_;
+  std::vector<FakeHostResolver::SingleResult> public_result_list_;
+  std::unique_ptr<FakeHostResolver> system_resolver_;
+  std::unique_ptr<FakeHostResolver> public_resolver_;
+};
+
+class FakeDnsConfigChangeManager
+    : public network::mojom::DnsConfigChangeManager {
+ public:
+  explicit FakeDnsConfigChangeManager(
+      network::mojom::DnsConfigChangeManagerRequest request);
+  ~FakeDnsConfigChangeManager() override;
+
+  // mojom::DnsConfigChangeManager implementation:
+  void RequestNotifications(
+      network::mojom::DnsConfigChangeManagerClientPtr client) override;
+
+  void SimulateDnsConfigChange();
+
+ private:
+  mojo::Binding<network::mojom::DnsConfigChangeManager> binding_;
+  network::mojom::DnsConfigChangeManagerClientPtr client_;
+};
 
 }  // namespace chrome_browser_net
 
diff --git a/chrome/browser/net/net_error_tab_helper.cc b/chrome/browser/net/net_error_tab_helper.cc
index 5a551c3..d3987643 100644
--- a/chrome/browser/net/net_error_tab_helper.cc
+++ b/chrome/browser/net/net_error_tab_helper.cc
@@ -6,10 +6,8 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "base/task/post_task.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/dns_probe_service.h"
+#include "chrome/browser/net/dns_probe_service_factory.h"
 #include "chrome/browser/net/net_error_diagnostics_dialog.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -48,28 +46,6 @@
 static NetErrorTabHelper::TestingState testing_state_ =
     NetErrorTabHelper::TESTING_DEFAULT;
 
-void OnDnsProbeFinishedOnIOThread(
-    const base::Callback<void(DnsProbeStatus)>& callback,
-    DnsProbeStatus result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
-                           base::BindOnce(callback, result));
-}
-
-// Can only access g_browser_process->io_thread() from the browser thread,
-// so have to pass it in to the callback instead of dereferencing it here.
-void StartDnsProbeOnIOThread(
-    const base::Callback<void(DnsProbeStatus)>& callback,
-    IOThread* io_thread) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  DnsProbeService* probe_service =
-      io_thread->globals()->dns_probe_service.get();
-
-  probe_service->ProbeDns(base::Bind(&OnDnsProbeFinishedOnIOThread, callback));
-}
-
 }  // namespace
 
 NetErrorTabHelper::~NetErrorTabHelper() {
@@ -193,12 +169,10 @@
 
   DVLOG(1) << "Starting DNS probe.";
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
-      base::BindOnce(&StartDnsProbeOnIOThread,
-                     base::Bind(&NetErrorTabHelper::OnDnsProbeFinished,
-                                weak_factory_.GetWeakPtr()),
-                     g_browser_process->io_thread()));
+  DnsProbeService* probe_service = DnsProbeServiceFactory::GetForContext(
+      web_contents()->GetBrowserContext());
+  probe_service->ProbeDns(base::BindOnce(&NetErrorTabHelper::OnDnsProbeFinished,
+                                         weak_factory_.GetWeakPtr()));
 }
 
 void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) {
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index a522ad0d..2cd0535 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -32,6 +32,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/download_test_observer.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "media/base/media_switches.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -502,7 +503,7 @@
   bool ExpectationsSatisfied() const override {
     int num_ad_resources = 0;
     for (auto& kv : page_resources_) {
-      if (kv.second->reported_as_ad_resource)
+      if (kv.second->reported_as_ad_resource && kv.second->is_complete)
         num_ad_resources++;
     }
     return num_ad_resources >= expected_minimum_num_ad_resources_ &&
@@ -529,6 +530,12 @@
          subresource_filter::testing::CreateSuffixRule("disallow.zip")});
   }
 
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(
+        switches::kAutoplayPolicy,
+        switches::autoplay::kNoUserGestureRequiredPolicy);
+  }
+
   void OpenLinkInFrame(const content::ToRenderFrameHost& adapter,
                        const std::string& link_id,
                        bool has_gesture) {
@@ -863,9 +870,9 @@
   GURL url = embedded_test_server()->GetURL("foo.com", "/frame_factory.html");
   ui_test_utils::NavigateToURL(browser(), url);
 
-  // This page will load an autoplay video.
-  contents->GetMainFrame()->ExecuteJavaScriptForTests(
-      base::ASCIIToUTF16("createAdFrame('multiple_mimes.html', 'test');"));
+  // This frame will load a video.
+  EXPECT_TRUE(
+      ExecJs(contents, "createAdFrame('multiple_mimes.html', 'test');"));
 
   // Intercept one of the resources loaded by "multiple_mimes.html" and load
   // enough bytes to trigger the intervention.
@@ -878,7 +885,7 @@
   waiter->AddMinimumAdResourceExpectation(8);
   waiter->Wait();
 
-  // Wait for the video to play.
+  // Wait for the video to autoplay in the frame.
   content::RenderFrameHost* ad_frame =
       ChildFrameAt(contents->GetMainFrame(), 0);
   EXPECT_EQ("true", content::EvalJsWithManualReply(
@@ -886,7 +893,8 @@
                         "var video = document.getElementsByTagName('video')[0];"
                         "video.onplaying = () => { "
                         "window.domAutomationController.send('true'); };"
-                        "video.play();"));
+                        "video.play();",
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Close all tabs to report metrics.
   browser()->tab_strip_model()->CloseAllTabs();
diff --git a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
index 479378f..3147c4cc 100644
--- a/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/data_saver_site_breakdown_metrics_observer.cc
@@ -37,7 +37,11 @@
   // will be called is in MetricsWebContentsObserver's destructor, which is
   // called in WebContents destructor.
   browser_context_ = navigation_handle->GetWebContents()->GetBrowserContext();
-  committed_host_ = navigation_handle->GetURL().HostNoBrackets();
+
+  // Use Virtual URL instead of actual host.
+  committed_host_ = navigation_handle->GetWebContents()
+                        ->GetLastCommittedURL()
+                        .HostNoBrackets();
   return CONTINUE_OBSERVING;
 }
 
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index d62727c..e06622a 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -1307,12 +1307,13 @@
   ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kSuccess));
   VerifyPreviewLoaded();
 
+  base::RunLoop().RunUntilIdle();
+
   // Navigate to an untracked (no preview) page before checking reported savings
   // to reduce flakiness.
-  ui_test_utils::NavigateToURL(browser(), GURL("http://www.google.com"));
+  ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
 
   EXPECT_EQ(GetTotalOriginalContentLength() - GetTotalDataUsage(), 40U);
-  EXPECT_EQ(GetDataUsage(), 20U);
 }
 
 IN_PROC_BROWSER_TEST_P(PreviewsLitePageServerBrowserTest,
diff --git a/chrome/browser/previews/previews_lite_page_decider.cc b/chrome/browser/previews/previews_lite_page_decider.cc
index 61c833be..17f8ed4b 100644
--- a/chrome/browser/previews/previews_lite_page_decider.cc
+++ b/chrome/browser/previews/previews_lite_page_decider.cc
@@ -340,11 +340,14 @@
   if (!drp_settings_ || !drp_settings_->data_reduction_proxy_service())
     return;
 
+  // The total data usage is tracked for all data in Chrome, so we only need to
+  // update the savings.
+  int64_t data_saved = original_bytes - network_bytes;
   drp_settings_->data_reduction_proxy_service()->UpdateDataUseForHost(
-      network_bytes, original_bytes, host);
+      0, data_saved, host);
 
   drp_settings_->data_reduction_proxy_service()->UpdateContentLengths(
-      network_bytes, original_bytes, true /* data_reduction_proxy_enabled */,
+      0, data_saved, true /* data_reduction_proxy_enabled */,
       data_reduction_proxy::DataReductionProxyRequestType::
           VIA_DATA_REDUCTION_PROXY,
       "text/html", true /* is_user_traffic */,
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle_manager.h b/chrome/browser/previews/previews_lite_page_navigation_throttle_manager.h
index 57f3493..a2b98623 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle_manager.h
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle_manager.h
@@ -38,7 +38,8 @@
   // Generates a new page id for a request to the previews server.
   virtual uint64_t GeneratePageID() = 0;
 
-  // Reports data savings to Data Saver.
+  // Reports data savings to Data Saver. Only the difference in |original_bytes|
+  // and |network_bytes| will be updated in the data saver calls.
   virtual void ReportDataSavings(int64_t network_bytes,
                                  int64_t original_bytes,
                                  const std::string& host) = 0;
diff --git a/chrome/browser/profile_resetter/profile_resetter.cc b/chrome/browser/profile_resetter/profile_resetter.cc
index 5efe37b..c59eac6 100644
--- a/chrome/browser/profile_resetter/profile_resetter.cc
+++ b/chrome/browser/profile_resetter/profile_resetter.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profile_resetter/brandcoded_default_settings.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -82,7 +81,6 @@
       template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
       pending_reset_flags_(0),
       cookies_remover_(nullptr),
-      ntp_service_(InstantServiceFactory::GetForProfile(profile)),
       weak_ptr_factory_(this) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(profile_);
@@ -130,7 +128,6 @@
       {STARTUP_PAGES, &ProfileResetter::ResetStartupPages},
       {PINNED_TABS, &ProfileResetter::ResetPinnedTabs},
       {SHORTCUTS, &ProfileResetter::ResetShortcuts},
-      {NTP_CUSTOMIZATIONS, &ProfileResetter::ResetNtpCustomizations},
   };
 
   ResettableFlags reset_triggered_for_flags = 0;
@@ -337,11 +334,6 @@
 #endif
 }
 
-void ProfileResetter::ResetNtpCustomizations() {
-  ntp_service_->ResetToDefault();
-  MarkAsDone(NTP_CUSTOMIZATIONS);
-}
-
 void ProfileResetter::OnTemplateURLServiceLoaded() {
   // TemplateURLService has loaded. If we need to clean search engines, it's
   // time to go on.
diff --git a/chrome/browser/profile_resetter/profile_resetter.h b/chrome/browser/profile_resetter/profile_resetter.h
index efba184..de5d5c5b 100644
--- a/chrome/browser/profile_resetter/profile_resetter.h
+++ b/chrome/browser/profile_resetter/profile_resetter.h
@@ -19,7 +19,6 @@
 #include "base/sequence_checker.h"
 #include "base/strings/string16.h"
 #include "chrome/browser/profile_resetter/brandcoded_default_settings.h"
-#include "chrome/browser/search/instant_service.h"
 #include "components/search_engines/template_url_service.h"
 #include "content/public/browser/browsing_data_remover.h"
 
@@ -29,10 +28,6 @@
 class CancellationFlag;
 }
 
-namespace {
-FORWARD_DECLARE_TEST(ProfileResetterTest, ResetNTPCustomizationsTest);
-}
-
 // This class allows resetting certain aspects of a profile to default values.
 // It is used in case the profile has been damaged due to malware or bad user
 // settings.
@@ -48,12 +43,11 @@
     STARTUP_PAGES = 1 << 5,
     PINNED_TABS = 1 << 6,
     SHORTCUTS = 1 << 7,
-    NTP_CUSTOMIZATIONS = 1 << 8,
     // Update ALL if you add new values and check whether the type of
     // ResettableFlags needs to be enlarged.
     ALL = DEFAULT_SEARCH_ENGINE | HOMEPAGE | CONTENT_SETTINGS |
           COOKIES_AND_SITE_DATA | EXTENSIONS | STARTUP_PAGES | PINNED_TABS |
-          SHORTCUTS | NTP_CUSTOMIZATIONS
+          SHORTCUTS
   };
 
   // Bit vector for Resettable enum.
@@ -75,8 +69,6 @@
   virtual bool IsActive() const;
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(::ProfileResetterTest, ResetNTPCustomizationsTest);
-
   // Marks |resettable| as done and triggers |callback_| if all pending jobs
   // have completed.
   void MarkAsDone(Resettable resettable);
@@ -89,7 +81,6 @@
   void ResetStartupPages();
   void ResetPinnedTabs();
   void ResetShortcuts();
-  void ResetNtpCustomizations();
 
   // BrowsingDataRemover::Observer:
   void OnBrowsingDataRemoverDone() override;
@@ -116,9 +107,6 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
-  // Used for resetting NTP customizations.
-  InstantService* ntp_service_;
-
   base::WeakPtrFactory<ProfileResetter> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ProfileResetter);
diff --git a/chrome/browser/profile_resetter/profile_resetter_unittest.cc b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
index c4f53833..a76350ec 100644
--- a/chrome/browser/profile_resetter/profile_resetter_unittest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
@@ -28,7 +28,6 @@
 #include "chrome/browser/profile_resetter/profile_reset_report.pb.h"
 #include "chrome/browser/profile_resetter/profile_resetter_test_base.h"
 #include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
-#include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
@@ -320,14 +319,6 @@
 }
 #endif  // defined(OS_WIN)
 
-// MockInstantService
-class MockInstantService : public InstantService {
- public:
-  explicit MockInstantService(Profile* profile) : InstantService(profile) {}
-  ~MockInstantService() override = default;
-
-  MOCK_METHOD0(ResetToDefault, void());
-};
 
 // helper functions -----------------------------------------------------------
 
@@ -1027,12 +1018,4 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(ProfileResetterTest, ResetNTPCustomizationsTest) {
-  MockInstantService mock_ntp_service(profile());
-  resetter_->ntp_service_ = &mock_ntp_service;
-
-  EXPECT_CALL(mock_ntp_service, ResetToDefault());
-  ResetAndWait(ProfileResetter::NTP_CUSTOMIZATIONS);
-}
-
 }  // namespace
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index 84c9118..271e8a2 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -3045,22 +3045,22 @@
       </message>
 
       <message desc="Spoken when a user navigates to the beginning of a font family's text, e.g. 'Arial start'" name="IDS_CHROMEVOX_FONT_FAMILY_START">
-        <ph name="font_family">$1</ph> start
+        <ph name="font_family">$1<ex>Arial</ex></ph> start
       </message>
       <message desc="Spoken when a user navigates to the end of a font family's text, e.g. 'Arial end'" name="IDS_CHROMEVOX_FONT_FAMILY_END">
-        <ph name="font_family">$1</ph> end
+        <ph name="font_family">$1<ex>Arial</ex></ph> end
       </message>
       <message desc="Spoken when a user navigates to the beginning of a font size's text, e.g. 'Size 12 start'" name="IDS_CHROMEVOX_FONT_SIZE_START">
-        Size <ph name="font_size">$1</ph> start
+        Size <ph name="font_size">$1<ex>12</ex></ph> start
       </message>
       <message desc="Spoken when a user navigates to the end of a font size's text, e.g. 'Size 12 end'" name="IDS_CHROMEVOX_FONT_SIZE_END">
-        Size <ph name="font_size">$1</ph> end
+        Size <ph name="font_size">$1<ex>12</ex></ph> end
       </message>
-      <message desc="Spoken when a user navigates to the beginning of a font color's text, e.g. 'Red start'" name="IDS_CHROMEVOX_FONT_COLOR_START">
-        <ph name="font_color">$1</ph> start
+      <message desc="Spoken when a user navigates to the beginning of a font color's text, e.g. 'Red, 100% opacity. start'" name="IDS_CHROMEVOX_FONT_COLOR_START">
+        <ph name="font_color">$1<ex>Red, 100% opacity.</ex></ph> start
       </message>
-      <message desc="Spoken when a user navigates to the end of a font color's text, e.g. 'Red end'" name="IDS_CHROMEVOX_FONT_COLOR_END">
-        <ph name="font_color">$1</ph> end
+      <message desc="Spoken when a user navigates to the end of a font color's text, e.g. 'Red, 100% opacity. end'" name="IDS_CHROMEVOX_FONT_COLOR_END">
+        <ph name="font_color">$1<ex>Red, 100% opacity.</ex></ph> end
       </message>
 
       <message desc="Shown to a user when they press a braille keyboard command that requires on screen keyboard to be enabled." name="IDS_CHROMEVOX_ENABLE_VIRTUAL_KEYBOARD">
@@ -3248,18 +3248,18 @@
 
       <message desc="Spoken to describe all rich text attributes of a node." name="IDS_CHROMEVOX_RICH_TEXT_ATTRIBUTES">
         Text formatting.
-        <ph name="font_size_string">$1</ph>
-        <ph name="color_string">$2</ph>
-        <ph name="bold_string">$3</ph>
-        <ph name="italic_string">$4</ph>
-        <ph name="underline_string">$5</ph>
-        <ph name="line_through_string">$6</ph>
-        <ph name="font_family_string">$7</ph>
+        <ph name="font_size_string">$1<ex>Size 12</ex></ph>
+        <ph name="color_string">$2<ex>Red, 100% opacity.</ex></ph>
+        <ph name="bold_string">$3<ex>Bold</ex></ph>
+        <ph name="italic_string">$4<ex>Italic</ex></ph>
+        <ph name="underline_string">$5<ex>Unerline</ex></ph>
+        <ph name="line_through_string">$6<ex>Line through</ex></ph>
+        <ph name="font_family_string">$7<ex>Arial</ex></ph>
       </message>
 
       <!-- Colors -->
       <message desc="Spoken to describe color and opacity of text" name="IDS_CHROMEVOX_COLOR_DESCRIPTION">
-        <ph name="color">$1</ph>, <ph name="opacity_percentage">$2</ph>% opacity.
+        <ph name="color">$1<ex>Red</ex></ph>, <ph name="opacity_percentage">$2<ex>50</ex></ph>% opacity.
       </message>
       <message desc="Spoken to describe color of text" name="IDS_CHROMEVOX_COLOR_BLACK">
         Black
diff --git a/chrome/browser/resources/downloads/images/1x/incognito_marker.png b/chrome/browser/resources/downloads/images/1x/incognito_marker.png
deleted file mode 100644
index 4d7b4f0..0000000
--- a/chrome/browser/resources/downloads/images/1x/incognito_marker.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/downloads/images/2x/incognito_marker.png b/chrome/browser/resources/downloads/images/2x/incognito_marker.png
deleted file mode 100644
index af60569..0000000
--- a/chrome/browser/resources/downloads/images/2x/incognito_marker.png
+++ /dev/null
Binary files differ
diff --git a/chrome/browser/resources/downloads/images/incognito_marker.svg b/chrome/browser/resources/downloads/images/incognito_marker.svg
new file mode 100644
index 0000000..e744e3a
--- /dev/null
+++ b/chrome/browser/resources/downloads/images/incognito_marker.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="#5F6368" fill-rule="nonzero"><circle cx="5.44" cy="10.371" r="1.411"/><path d="M8 0C3.578 0 0 3.578 0 8c0 4.422 3.578 8 8 8 4.422 0 8-3.578 8-8 0-4.422-3.578-8-8-8zM6.095 3.28a.557.557 0 0 1 .705-.335l1.178.393 1.171-.393a.573.573 0 0 1 .706.335l1.287 3.433H4.815l1.28-3.433zm4.414 9.062a1.97 1.97 0 0 1-1.956-1.804c-.546-.349-.99-.13-1.164-.014a1.96 1.96 0 0 1-1.956 1.81 1.977 1.977 0 0 1-1.971-1.97c0-1.084.887-1.971 1.97-1.971.932 0 1.71.647 1.913 1.52a1.547 1.547 0 0 1 1.237.007 1.97 1.97 0 0 1 1.913-1.527c1.083 0 1.97.887 1.97 1.97a1.948 1.948 0 0 1-1.956 1.979zm2.538-4.502H2.91v-.567h10.138v.567z"/><circle cx="10.509" cy="10.371" r="1.411"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/downloads/item.html b/chrome/browser/resources/downloads/item.html
index 70a22a11..fb3924e 100644
--- a/chrome/browser/resources/downloads/item.html
+++ b/chrome/browser/resources/downloads/item.html
@@ -255,12 +255,13 @@
       }
 
       #incognito {
+        -webkit-mask-image: url(chrome://downloads/images/incognito_marker.svg);
+        background-color: var(--cr-secondary-text-color);
         bottom: 20px;
-        content: -webkit-image-set(
-            url(chrome://downloads/images/1x/incognito_marker.png) 1x,
-            url(chrome://downloads/images/2x/incognito_marker.png) 2x);
+        height: 16px;
         position: absolute;
         right: 16px;
+        width: 16px;
       }
 
       :host-context([dir='rtl']) #incognito {
diff --git a/chrome/browser/resources/policy/policy.css b/chrome/browser/resources/policy/policy.css
index 8aa5fcc2..17241e8 100644
--- a/chrome/browser/resources/policy/policy.css
+++ b/chrome/browser/resources/policy/policy.css
@@ -52,11 +52,11 @@
 
 .name {
   border-inline-end: 1px solid rgba(0, 0, 0, .06);
-  flex: 0 0 20%;
+  flex: 0 0 15%;
 }
 
 .value {
-  flex: 0 0 40%;
+  flex: 0 0 35%;
 }
 
 .row.header {
@@ -75,10 +75,9 @@
   white-space: nowrap;
 }
 
-.expanded,
-.policy.row:hover {
+.expanded .policy.row:not(.conflict),
+.policy.row:not(.conflict):hover {
   background-color: rgb(250, 250, 250);
-  cursor: pointer;
 }
 
 .messages.row .value,
@@ -91,6 +90,7 @@
 }
 
 .messages.row .name,
+.conflict.row .name,
 .value.row .name {
   text-align: end;
 }
@@ -141,12 +141,42 @@
   padding: 12px;
 }
 
-.no-help-link .name-link {
+a {
+  color: rgb(26, 115, 232);
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.toggle {
+  cursor: pointer;
+}
+
+.name .link {
+  align-items: center;
+  display: flex;
+}
+
+.link span {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.link img {
+  height: 12px;
+  width: 12px;
+}
+
+.no-help-link .name .link {
   color: inherit;
   pointer-events: none;
   text-decoration: none;
 }
 
+.no-help-link .link img {
+  display: none;
+}
+
 <if expr="android">
 .name {
   flex: 0 0 50%;
diff --git a/chrome/browser/resources/policy/policy.html b/chrome/browser/resources/policy/policy.html
index fe0b4ef..e2e8d4e 100644
--- a/chrome/browser/resources/policy/policy.html
+++ b/chrome/browser/resources/policy/policy.html
@@ -117,27 +117,53 @@
           <div class="source">$i18n{headerSource}</div>
           <div class="scope">$i18n{headerScope}</div>
           <div class="level">$i18n{headerLevel}</div>
-          <div class="messages">$i18n{headerWarning}</div>
+          <div class="messages">$i18n{headerStatus}</div>
+          <div class="toggle"></div>
         </div>
         <div class="no-policy">$i18n{noPoliciesSet}</div>
       </div>
     </div>
 
+    <div class="policy-conflict-data" id="policy-conflict-template" hidden>
+      <div class="policy conflict row">
+          <div class="name">$i18n{conflict}</div>
+          <div class="value"></div>
+          <div class="source"></div>
+          <div class="scope"></div>
+          <div class="level"></div>
+          <div class="messages"></div>
+          <div class="toggle"></div>
+      </div>
+      <div class="value row">
+        <div class="name"></div>
+        <div class="value"></div>
+      </div>
+    </div>
+
     <div class="policy-data" id="policy-template">
       <div class="policy row">
-        <div class="name"><a class="name-link"></a></div>
+        <div class="name">
+          <a class="link" target="_blank">
+            <span></span>
+            <img src="../../../../ui/webui/resources/images/open_in_new.svg">
+          </a>
+        </div>
         <div class="value"></div>
         <div class="source"></div>
         <div class="scope"></div>
         <div class="level"></div>
         <div class="messages"></div>
+        <div class="toggle">
+          <a is="action-link" class="show-more">$i18n{showMore}</a>
+          <a is="action-link" class="show-less" hidden>$i18n{showLess}</a>
+        </div>
       </div>
       <div class="value row" hidden>
         <div class="name">$i18n{value}</div>
         <div class="value"></div>
       </div>
       <div class="messages row" hidden>
-        <div class="name">$i18n{messages}</div>
+        <div class="name">$i18n{warning}</div>
         <div class="value"></div>
       </div>
     </div>
diff --git a/chrome/browser/resources/policy/policy_base.js b/chrome/browser/resources/policy/policy_base.js
index d8add49..39234d3 100644
--- a/chrome/browser/resources/policy/policy_base.js
+++ b/chrome/browser/resources/policy/policy_base.js
@@ -24,6 +24,16 @@
 
 /**
  * @typedef {{
+ *    level: string,
+ *    scope: string,
+ *    source: string,
+ *    value: any,
+ * }}
+ */
+policy.Conflict;
+
+/**
+ * @typedef {{
  *    name: string,
  *    level: string,
  *    link: ?string,
@@ -31,6 +41,7 @@
  *    source: string,
  *    error: string,
  *    value: any,
+ *    conflicts: ?Array<!Conflict>,
  * }}
  */
 policy.Policy;
@@ -142,6 +153,39 @@
   };
 
   /**
+   * A single policy conflict's entry in the policy table.
+   * @constructor
+   * @extends {HTMLDivElement}
+   */
+  const PolicyConflict = cr.ui.define(function() {
+    const node = $('policy-conflict-template').cloneNode(true);
+    node.removeAttribute('id');
+    return node;
+  });
+
+  PolicyConflict.prototype = {
+    // Set up the prototype chain.
+    __proto__: HTMLDivElement.prototype,
+
+    decorate: function() {
+      this.querySelector('.policy.row')
+          .addEventListener('click', this.toggleExpanded_);
+    },
+
+    /** @param {Conflict} conflict */
+    initialize(conflict) {
+      this.querySelector('.scope').textContent = loadTimeData.getString(
+          conflict.scope == 'user' ? 'scopeUser' : 'scopeDevice');
+      this.querySelector('.level').textContent = loadTimeData.getString(
+          conflict.level == 'recommended' ? 'levelRecommended' :
+                                            'levelMandatory');
+      this.querySelector('.source').textContent =
+          loadTimeData.getString(conflict.source);
+      this.querySelector('.value.row .value').textContent = conflict.value;
+    }
+  };
+
+  /**
    * A single policy's entry in the policy table.
    * @constructor
    * @extends {HTMLDivElement}
@@ -160,8 +204,8 @@
      * Initialization function for the cr.ui framework.
      */
     decorate: function() {
-      const policyRowDisplay = this.querySelector('.policy.row');
-      policyRowDisplay.addEventListener('click', this.toggleExpanded_);
+      const toggle = this.querySelector('.policy.row .toggle');
+      toggle.addEventListener('click', this.toggleExpanded_);
     },
 
     /** @param {Policy} policy */
@@ -175,13 +219,16 @@
       /** @private {boolean} */
       this.hasMessages_ = !!policy.error;
 
+      /** @private {boolean} */
+      this.hasConflicts_ = !!policy.conflicts;
+
       // Populate the name column.
-      const nameDisplay = this.querySelector('.name-link');
+      const nameDisplay = this.querySelector('.name .link span');
       nameDisplay.textContent = policy.name;
       if (policy.link) {
-        nameDisplay.href = policy.link;
-        nameDisplay.title =
-            loadTimeData.getStringF('policyLearnMore', policy.name);
+        const link = this.querySelector('.name .link');
+        link.href = policy.link;
+        link.title = loadTimeData.getStringF('policyLearnMore', policy.name);
       } else {
         this.classList.add('no-help-link');
       }
@@ -209,10 +256,6 @@
         const valueDisplay = this.querySelector('.value');
         valueDisplay.textContent = truncatedValue;
 
-        const messagesDisplay = this.querySelector('.messages');
-        messagesDisplay.textContent =
-            this.hasMessages_ ? loadTimeData.getString('messages') : '';
-        messagesDisplay.hidden = !this.hasMessages_;
 
         const valueRowContentDisplay = this.querySelector('.value.row .value');
         valueRowContentDisplay.textContent = policy.value;
@@ -221,13 +264,37 @@
             this.querySelector('.messages.row .value');
         messageRowContentDisplay.textContent = policy.error;
 
+        const messagesDisplay = this.querySelector('.messages');
+        const messagesNotice =
+            this.hasMessages_ ? loadTimeData.getString('warning') : '';
+        const conflictsNotice =
+            this.hasConflicts_ ? loadTimeData.getString('conflict') : '';
+        const notice = (messagesNotice && conflictsNotice) ?
+            loadTimeData.getString('warningAndConflicts') :
+            messagesNotice || conflictsNotice || loadTimeData.getString('ok');
+        messagesDisplay.textContent = notice;
+
         // <if expr="android">
         const valueRowDisplay = this.querySelector('.value.row');
         valueRowDisplay.hidden = false;
         valueDisplay.hidden = true;
         levelDisplay.hidden = true;
         scopeDisplay.hidden = true;
+        this.querySelector('.toggle').hidden = true;
+        row.querySelectorAll('.policy-conflict-data')
+            .forEach(row => row.hidden = false);
         // </if>
+
+        if (policy.conflicts) {
+          policy.conflicts.forEach(conflict => {
+            const row = new PolicyConflict;
+            row.initialize(conflict);
+            this.appendChild(row);
+          });
+        }
+      } else {
+        const messagesDisplay = this.querySelector('.messages');
+        messagesDisplay.textContent = loadTimeData.getString('unset');
       }
     },
 
@@ -237,13 +304,24 @@
      */
     toggleExpanded_: function() {
       // <if expr="not android">
-      const row = this.parentElement;
+      const row = this.parentElement.parentElement;
       const messageRowDisplay = row.querySelector('.messages.row');
       const valueRowDisplay = row.querySelector('.value.row');
       valueRowDisplay.hidden = !valueRowDisplay.hidden;
-      if (row.hasMessages) {
+      if (valueRowDisplay.hidden) {
+        row.classList.remove('expanded');
+      } else {
+        row.classList.add('expanded');
+      }
+
+      const messagesDisplay = row.querySelector('.messages');
+      this.querySelector('.show-more').hidden = !valueRowDisplay.hidden;
+      this.querySelector('.show-less').hidden = valueRowDisplay.hidden;
+      if (messagesDisplay.textContent !== loadTimeData.getString('ok')) {
         messageRowDisplay.hidden = !messageRowDisplay.hidden;
       }
+      row.querySelectorAll('.policy-conflict-data')
+          .forEach(row => row.hidden = !row.hidden);
       // </if>
     },
   };
@@ -276,7 +354,7 @@
     update(dataModel) {
       // Clear policies
       const mainContent = this.querySelector('.main');
-      const policies = this.querySelectorAll('.policies-data');
+      const policies = this.querySelectorAll('.policy-data');
       this.querySelector('.header').textContent = dataModel.name;
       this.querySelector('.id').textContent = dataModel.id;
       this.querySelector('.id').hidden = !dataModel.id;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
index 3e25bdeb..7623098 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
@@ -18,6 +18,12 @@
     /** @return {!Promise<!Array<!nux.NtpBackgroundData>>} */
     getBackgrounds() {}
 
+    /**
+     * @param {string} url
+     * @return {!Promise<void>}
+     */
+    preloadImage(url) {}
+
     /** @param {number} id */
     setBackground(id) {}
   }
@@ -30,6 +36,16 @@
     }
 
     /** @override */
+    preloadImage(url) {
+      return new Promise((resolve, reject) => {
+        const preloadedImage = new Image();
+        preloadedImage.onerror = reject;
+        preloadedImage.onload = resolve;
+        preloadedImage.src = url;
+      });
+    }
+
+    /** @override */
     setBackground(id) {
       chrome.send('setBackground', [id]);
     }
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
index f1f15bc..2ad633d 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
@@ -20,6 +20,41 @@
         text-align: center;
       }
 
+      #backgroundPreview {
+        background-position: center center;
+        background-repeat: no-repeat;
+        background-size: cover;
+        bottom: 0;
+        left: 0;
+        opacity: 0;
+        position: fixed;
+        right: 0;
+        top: 0;
+        transition: background 300ms, opacity 400ms;
+      }
+
+      #backgroundPreview.active {
+        opacity: 1;
+      }
+
+      #backgroundPreview::before {
+        /* Copied from browser/resources/local_ntp/custom_backgrounds.js */
+        background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
+        /* Pseudo element needs some content (even an empty string) to be
+         * displayed. */
+        content: '';
+        display: block;
+        height: 100%;
+        width: 100%;
+      }
+
+      .content {
+        /* Put a non-static position on the content so that it can have a
+         * higher stacking level than its previous sibling,
+         * the #backgroundPreview element. */
+        position: relative;
+      }
+
       .ntp-background-logo {
         content: -webkit-image-set(
             url(chrome://welcome/images/ntp_background_1x.png) 1x,
@@ -37,6 +72,11 @@
         margin: 0;
         margin-bottom: 46px;
         outline: none;
+        transition: color 400ms;
+      }
+
+      #backgroundPreview.active + .content h1 {
+        color: white;
       }
 
       .ntp-backgrounds-grid {
@@ -116,6 +156,11 @@
         margin-top: 56px;
       }
 
+      .skip-button-container {
+        background: white;
+        border-radius: 4px;
+      }
+
       iron-icon[icon='cr:chevron-right'] {
         height: 20px;
         margin-inline-end: -10px;
@@ -127,35 +172,47 @@
         transform: scaleX(-1);
       }
     </style>
-    <div class="ntp-background-logo"></div>
-    <h1 tabindex="-1">$i18n{ntpBackgroundDescription}</h1>
-
-    <div class="ntp-backgrounds-grid">
-      <template is="dom-repeat" items="[[backgrounds_]]">
-        <button
-            active$="[[isSelectedBackground_(item, selectedBackground_)]]"
-            aria-pressed$="[[getAriaPressedValue_(item, selectedBackground_)]]"
-            class="ntp-background-grid-button"
-            on-click="onBackgroundClick_"
-            on-keyup="onBackgroundKeyUp_"
-            on-pointerdown="onBackgroundPointerDown_">
-          <div class$="ntp-background-thumbnail [[item.thumbnailClass]]"></div>
-          <div class="ntp-background-title">[[item.title]]</div>
-        </button>
-      </template>
+    <div
+        id="backgroundPreview"
+        on-transitionend="onBackgroundPreviewTransitionEnd_">
     </div>
 
-    <div class="button-bar">
-      <paper-button on-click="onSkipClicked_">
-        $i18n{skip}
-      </paper-button>
-      <step-indicator model="[[indicatorModel]]"></step-indicator>
-      <paper-button class="action-button"
-          disabled$="[[!hasValidSelectedBackground_(selectedBackground_)]]"
-          on-click="onNextClicked_">
-        $i18n{next}
-        <iron-icon icon="cr:chevron-right"></iron-icon>
-      </paper-button>
+    <div class="content">
+      <div class="ntp-background-logo"></div>
+      <h1 tabindex="-1">$i18n{ntpBackgroundDescription}</h1>
+
+      <div class="ntp-backgrounds-grid">
+        <template is="dom-repeat" items="[[backgrounds_]]">
+          <button
+              active$="[[isSelectedBackground_(item, selectedBackground_)]]"
+              aria-pressed$="[[getAriaPressedValue_(item,
+                                                    selectedBackground_)]]"
+              class="ntp-background-grid-button"
+              on-click="onBackgroundClick_"
+              on-keyup="onBackgroundKeyUp_"
+              on-pointerdown="onBackgroundPointerDown_">
+            <div
+                class$="ntp-background-thumbnail [[item.thumbnailClass]]">
+            </div>
+            <div class="ntp-background-title">[[item.title]]</div>
+          </button>
+        </template>
+      </div>
+
+      <div class="button-bar">
+        <div class="skip-button-container">
+          <paper-button on-click="onSkipClicked_">
+            $i18n{skip}
+          </paper-button>
+        </div>
+        <step-indicator model="[[indicatorModel]]"></step-indicator>
+        <paper-button class="action-button"
+            disabled$="[[!hasValidSelectedBackground_(selectedBackground_)]]"
+            on-click="onNextClicked_">
+          $i18n{next}
+          <iron-icon icon="cr:chevron-right"></iron-icon>
+        </paper-button>
+      </div>
     </div>
   </template>
 
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
index 475a90c..a9bf6b8 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
@@ -17,7 +17,10 @@
     indicatorModel: Object,
 
     /** @private {?nux.NtpBackgroundData} */
-    selectedBackground_: Object,
+    selectedBackground_: {
+      observer: 'onSelectedBackgroundChange_',
+      type: Object,
+    },
   },
 
   /** @private {?Array<!nux.NtpBackgroundData>} */
@@ -38,13 +41,17 @@
       thumbnailClass: '',
       title: this.i18n('ntpBackgroundDefault'),
     };
-    this.selectedBackground_ = defaultBackground;
-    this.ntpBackgroundProxy_.getBackgrounds().then((backgrounds) => {
-      this.backgrounds_ = [
-        defaultBackground,
-        ...backgrounds,
-      ];
-    });
+    if (!this.selectedBackground_) {
+      this.selectedBackground_ = defaultBackground;
+    }
+    if (!this.backgrounds_) {
+      this.ntpBackgroundProxy_.getBackgrounds().then((backgrounds) => {
+        this.backgrounds_ = [
+          defaultBackground,
+          ...backgrounds,
+        ];
+      });
+    }
   },
 
   /**
@@ -71,6 +78,33 @@
     return background == this.selectedBackground_;
   },
 
+  /** @private */
+  onSelectedBackgroundChange_: function() {
+    const id = this.selectedBackground_.id;
+
+    if (id > -1) {
+      const imageUrl = this.selectedBackground_.imageUrl;
+      this.ntpBackgroundProxy_.preloadImage(imageUrl).then(() => {
+        if (this.selectedBackground_.id === id) {
+          this.$.backgroundPreview.classList.add('active');
+          this.$.backgroundPreview.style.backgroundImage = `url(${imageUrl})`;
+        }
+      });
+    } else {
+      this.$.backgroundPreview.classList.remove('active');
+    }
+  },
+
+  /** @private */
+  onBackgroundPreviewTransitionEnd_: function() {
+    // Whenever the #backgroundPreview transitions to a non-active, hidden
+    // state, remove the background image. This way, when the element
+    // transitions back to active, the previous background is not displayed.
+    if (!this.$.backgroundPreview.classList.contains('active')) {
+      this.$.backgroundPreview.style.backgroundImage = '';
+    }
+  },
+
   /**
    * @param {!{model: !{item: !nux.NtpBackgroundData}}} e
    * @private
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 1fe3059..3179fb4 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -711,11 +711,6 @@
   return true;
 }
 
-void InstantService::ResetToDefault() {
-  ResetCustomLinks();
-  ResetCustomBackgroundThemeInfo();
-}
-
 bool InstantService::IsCustomBackgroundPrefValid(GURL& custom_background_url) {
   const base::DictionaryValue* background_info =
       profile_->GetPrefs()->GetDictionary(prefs::kNtpCustomBackgroundDict);
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index 4ef495d..7bf4bdd 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -101,8 +101,8 @@
   bool UndoCustomLinkAction();
   // Invoked when the Instant page wants to delete all custom links and use Most
   // Visited sites instead. Returns false and does nothing if the profile is
-  // using a non-Google search provider. Marked virtual for mocking in tests.
-  virtual bool ResetCustomLinks();
+  // using a non-Google search provider.
+  bool ResetCustomLinks();
 
   // Invoked by the InstantController to update theme information for NTP.
   //
@@ -146,10 +146,6 @@
   // Check if a custom background has been set by the user.
   bool IsCustomBackgroundSet();
 
-  // Reset all NTP customizations to default. Marked virtual for mocking in
-  // tests.
-  virtual void ResetToDefault();
-
  private:
   class SearchProviderObserver;
 
@@ -194,8 +190,7 @@
 
   void ApplyCustomBackgroundThemeInfo();
 
-  // Marked virtual for mocking in tests.
-  virtual void ResetCustomBackgroundThemeInfo();
+  void ResetCustomBackgroundThemeInfo();
 
   void FallbackToDefaultThemeInfo();
 
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index 3f5dc50..5f2074f8 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -20,7 +20,6 @@
 #include "chrome/common/url_constants.h"
 #include "components/ntp_tiles/constants.h"
 #include "components/ntp_tiles/ntp_tile.h"
-#include "components/ntp_tiles/pref_names.h"
 #include "components/ntp_tiles/section_type.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -47,15 +46,6 @@
   return background_info;
 }
 
-class MockInstantService : public InstantService {
- public:
-  explicit MockInstantService(Profile* profile) : InstantService(profile) {}
-  ~MockInstantService() override = default;
-
-  MOCK_METHOD0(ResetCustomLinks, bool());
-  MOCK_METHOD0(ResetCustomBackgroundThemeInfo, void());
-};
-
 }  // namespace
 
 using InstantServiceTest = InstantUnitTestBase;
@@ -439,13 +429,6 @@
   EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
 }
 
-TEST_F(InstantServiceTest, TestResetToDefault) {
-  MockInstantService mock_instant_service_(profile());
-  EXPECT_CALL(mock_instant_service_, ResetCustomLinks());
-  EXPECT_CALL(mock_instant_service_, ResetCustomBackgroundThemeInfo());
-  mock_instant_service_.ResetToDefault();
-}
-
 class InstantServiceThemeTest : public InstantServiceTest {
  public:
   InstantServiceThemeTest() {}
diff --git a/chrome/browser/sync_file_system/logger.h b/chrome/browser/sync_file_system/logger.h
index e3d4520..90abc24 100644
--- a/chrome/browser/sync_file_system/logger.h
+++ b/chrome/browser/sync_file_system/logger.h
@@ -24,7 +24,7 @@
 // This function can be called from any thread.
 void Log(logging::LogSeverity level,
          const base::Location& location,
-         _Printf_format_string_ const char* format,
+         const char* format,
          ...) PRINTF_FORMAT(3, 4);
 
 // Returns the log history.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e2f1fea..b9a19c7 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1917,6 +1917,8 @@
       "webui/welcome/nux/email_providers_list.h",
       "webui/welcome/nux/google_apps_handler.cc",
       "webui/welcome/nux/google_apps_handler.h",
+      "webui/welcome/nux/ntp_background_fetcher.cc",
+      "webui/welcome/nux/ntp_background_fetcher.h",
       "webui/welcome/nux/ntp_background_handler.cc",
       "webui/welcome/nux/ntp_background_handler.h",
       "webui/welcome/nux/set_as_default_handler.cc",
@@ -2944,9 +2946,11 @@
         "views/create_application_shortcut_view.h",
         "views/ssl_client_certificate_selector.cc",
         "views/ssl_client_certificate_selector.h",
+      ]
+    }
 
-        # TODO(ellyjones): Mus is not supported on Mac (there is no ui::Window
-        # apart from aura::Window, which is also not supported).
+    if (enable_mus) {
+      sources += [
         "views/ime_driver/ime_driver_mus.cc",
         "views/ime_driver/ime_driver_mus.h",
         "views/ime_driver/remote_text_input_client.cc",
diff --git a/chrome/browser/ui/app_list/app_launch_event_logger.cc b/chrome/browser/ui/app_list/app_launch_event_logger.cc
index 04d7489..bf58860 100644
--- a/chrome/browser/ui/app_list/app_launch_event_logger.cc
+++ b/chrome/browser/ui/app_list/app_launch_event_logger.cc
@@ -41,7 +41,7 @@
 
 namespace {
 
-constexpr int kNumRandomAppsToLog = 5;
+constexpr unsigned int kNumRandomAppsToLog = 5;
 const char kArcScheme[] = "arc://";
 const char kExtensionSchemeWithDelimiter[] = "chrome-extension://";
 
@@ -75,6 +75,26 @@
   return round(pow(base, round(log(value) / log(base))));
 }
 
+// Selects a random sample of size |sample_size| from |population|.
+std::vector<std::string> Sample(const std::vector<std::string>& population,
+                                unsigned int sample_size) {
+  std::vector<std::string> sample;
+  unsigned int index = 0;
+  // Reservoir sampling.
+  for (const std::string& candidate : population) {
+    if (index < sample_size) {
+      sample.push_back(candidate);
+    } else {
+      const uint64_t r = base::RandGenerator(index + 1);
+      if (r < sample_size) {
+        sample[r] = candidate;
+      }
+    }
+    index++;
+  }
+  return sample;
+}
+
 int HourOfDay(base::Time time) {
   base::Time::Exploded exploded;
   time.LocalExplode(&exploded);
@@ -329,26 +349,28 @@
 
 std::vector<std::string> AppLaunchEventLogger::ChooseAppsToLog(
     const std::string clicked_app_id) {
-  int index = 0;
   bool has_clicked_app = false;
-  std::vector<std::string> apps;
-
-  // Reservoir sampling, but must also include clicked_app_id if it is
-  // present.
+  std::vector<std::string> clicked_apps;
+  std::vector<std::string> unclicked_apps;
+  // Group apps by whether they have been clicked on. Do not include the
+  // currently clicked app.
   for (auto& app : app_features_map_) {
     if (app.first == clicked_app_id) {
       has_clicked_app = true;
       continue;
     }
-    if (index < kNumRandomAppsToLog) {
-      apps.push_back(app.first);
+    if (app.second.has_most_recently_used_index()) {
+      clicked_apps.push_back(app.first);
     } else {
-      const uint64_t r = base::RandGenerator(index + 1);
-      if (r < kNumRandomAppsToLog) {
-        apps[r] = app.first;
-      }
+      unclicked_apps.push_back(app.first);
     }
-    index++;
+  }
+
+  std::vector<std::string> apps(Sample(clicked_apps, kNumRandomAppsToLog));
+  if (apps.size() < kNumRandomAppsToLog) {
+    std::vector<std::string> unclicked_sample(
+        Sample(unclicked_apps, kNumRandomAppsToLog - apps.size()));
+    apps.insert(apps.end(), unclicked_sample.begin(), unclicked_sample.end());
   }
   if (has_clicked_app) {
     apps.push_back(clicked_app_id);
@@ -480,8 +502,8 @@
                                        kTotalHoursBucketSizeMultiplier))
       .Record(ukm::UkmRecorder::Get());
 
-  // Log click data about the app clicked on and up to five other apps chosen at
-  // random. This represents the state of the data immediately before the click.
+  // Log click data about the app clicked on and up to five other apps. This
+  // represents the state of the data immediately before the click.
   const std::vector<std::string> apps_to_log =
       ChooseAppsToLog(app_launch_event.app_id());
 
diff --git a/chrome/browser/ui/app_list/app_launch_event_logger.h b/chrome/browser/ui/app_list/app_launch_event_logger.h
index cae399c..15589693 100644
--- a/chrome/browser/ui/app_list/app_launch_event_logger.h
+++ b/chrome/browser/ui/app_list/app_launch_event_logger.h
@@ -92,7 +92,12 @@
                             const std::string& app_id,
                             const std::string& arc_package_name,
                             const std::string& pwa_url);
-  // Randomly chooses up to five apps to log, plus the app clicked on.
+  // Chooses up to five apps to log, plus the app clicked on. Randomly chooses
+  // up to five apps that have previously been clicked on. These are preferred
+  // as they have metrics about past click behaviour. If there are fewer than
+  // five of these apps, then, in addition, randomly chooses from apps not
+  // clicked on, to get a total of five apps. If there are fewer than five apps
+  // that can be logged on the device, logs every app once.
   std::vector<std::string> ChooseAppsToLog(const std::string clicked_app_id);
   // Records a UMA histogram of the app type clicked on.
   void RecordAppTypeClicked(AppLaunchEvent_AppType app_type);
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index 906f8bf..b655585 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -185,7 +185,7 @@
 
 void ChromeBrowserMainExtraPartsAsh::PreProfileInit() {
   // IME driver must be available at login screen, so initialize before profile.
-  IMEDriver::Register();
+  IMEDriverMus::Register();
 
   // NetworkConnect handles the network connection state machine for the UI.
   network_connect_delegate_ =
diff --git a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc b/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
index c10a5e7..e98cf63 100644
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
+++ b/chrome/browser/ui/ash/fake_tablet_mode_controller.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/ash/fake_tablet_mode_controller.h"
 
+#include <utility>
+
 FakeTabletModeController::FakeTabletModeController() : binding_(this) {}
 
 FakeTabletModeController::~FakeTabletModeController() = default;
@@ -19,3 +21,9 @@
     ash::mojom::TabletModeClientPtr client) {
   was_client_set_ = true;
 }
+
+void FakeTabletModeController::SetTabletModeEnabledForTesting(
+    bool enabled,
+    SetTabletModeEnabledForTestingCallback callback) {
+  std::move(callback).Run(enabled);
+}
diff --git a/chrome/browser/ui/ash/fake_tablet_mode_controller.h b/chrome/browser/ui/ash/fake_tablet_mode_controller.h
index 5ec6ed0..45e9c66 100644
--- a/chrome/browser/ui/ash/fake_tablet_mode_controller.h
+++ b/chrome/browser/ui/ash/fake_tablet_mode_controller.h
@@ -23,6 +23,9 @@
 
   // ash::mojom::TabletModeController:
   void SetClient(ash::mojom::TabletModeClientPtr client) override;
+  void SetTabletModeEnabledForTesting(
+      bool enabled,
+      SetTabletModeEnabledForTestingCallback callback) override;
 
  private:
   mojo::Binding<ash::mojom::TabletModeController> binding_;
diff --git a/chrome/browser/ui/ash/tablet_mode_client.cc b/chrome/browser/ui/ash/tablet_mode_client.cc
index 4eee9d4..1a8f521 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.cc
+++ b/chrome/browser/ui/ash/tablet_mode_client.cc
@@ -60,6 +60,14 @@
   return g_tablet_mode_client_instance;
 }
 
+void TabletModeClient::SetTabletModeEnabledForTesting(
+    bool enabled,
+    ash::mojom::TabletModeController::SetTabletModeEnabledForTestingCallback
+        callback) {
+  tablet_mode_controller_->SetTabletModeEnabledForTesting(enabled,
+                                                          std::move(callback));
+}
+
 void TabletModeClient::AddObserver(TabletModeClientObserver* observer) {
   observers_.AddObserver(observer);
 }
diff --git a/chrome/browser/ui/ash/tablet_mode_client.h b/chrome/browser/ui/ash/tablet_mode_client.h
index 690938b6..60ec108 100644
--- a/chrome/browser/ui/ash/tablet_mode_client.h
+++ b/chrome/browser/ui/ash/tablet_mode_client.h
@@ -37,6 +37,15 @@
 
   bool tablet_mode_enabled() const { return tablet_mode_enabled_; }
 
+  // Enables or disabled tablet mode. For testing only since it disables the
+  // Accelerometer and PowerManager observers from the TabletModeController.
+  // Thus preventing to physically switch to/from tablet mode. |callback| is
+  // called when the operation has completed.
+  void SetTabletModeEnabledForTesting(
+      bool enabled,
+      ash::mojom::TabletModeController::SetTabletModeEnabledForTestingCallback
+          callback);
+
   // Adds the observer and immediately triggers it with the initial state.
   void AddObserver(TabletModeClientObserver* observer);
 
@@ -61,7 +70,7 @@
   // Binds this object to its mojo interface and sets it as the ash client.
   void BindAndSetClient();
 
-  // Enables/disables mobile-like bahvior for webpages in existing browsers, as
+  // Enables/disables mobile-like behavior for webpages in existing browsers, as
   // well as starts observing new browser pages if |enabled| is true.
   void SetMobileLikeBehaviorEnabled(bool enabled);
 
diff --git a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
index e787c79e..62cd4af 100644
--- a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.cc
@@ -6,6 +6,8 @@
 
 #include <stddef.h>
 
+#include "chrome/browser/autofill/strike_database_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/autofill/local_card_migration_bubble.h"
 #include "chrome/browser/ui/autofill/popup_constants.h"
 #include "chrome/browser/ui/browser.h"
@@ -13,6 +15,9 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/local_card_migration_strike_database.h"
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/navigation_handle.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -80,6 +85,7 @@
 void LocalCardMigrationBubbleControllerImpl::OnConfirmButtonClicked() {
   DCHECK(local_card_migration_bubble_closure_);
   std::move(local_card_migration_bubble_closure_).Run();
+  should_add_strikes_on_bubble_close_ = false;
 
   AutofillMetrics::LogLocalCardMigrationBubbleUserInteractionMetric(
       AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_CLOSED_ACCEPTED, is_reshow_);
@@ -95,6 +101,14 @@
 void LocalCardMigrationBubbleControllerImpl::OnBubbleClosed() {
   local_card_migration_bubble_ = nullptr;
   UpdateIcon();
+  if (should_add_strikes_on_bubble_close_ &&
+      base::FeatureList::IsEnabled(
+          features::kAutofillSaveCreditCardUsesStrikeSystemV2) &&
+      base::FeatureList::IsEnabled(
+          features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
+    should_add_strikes_on_bubble_close_ = false;
+    AddStrikesForBubbleClose();
+  }
 }
 
 base::TimeDelta LocalCardMigrationBubbleControllerImpl::Elapsed() const {
@@ -181,6 +195,14 @@
   location_bar->UpdateLocalCardMigrationIcon();
 }
 
+void LocalCardMigrationBubbleControllerImpl::AddStrikesForBubbleClose() {
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database(
+      StrikeDatabaseFactory::GetForProfile(
+          Profile::FromBrowserContext(web_contents()->GetBrowserContext())));
+  local_card_migration_strike_database.AddStrikes(
+      LocalCardMigrationStrikeDatabase::kStrikesToAddWhenBubbleClosed);
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(LocalCardMigrationBubbleControllerImpl)
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
index 1ec4c266..42df8e8 100644
--- a/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
+++ b/chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h
@@ -72,6 +72,10 @@
   // Update the visibility and toggled state of the Omnibox save card icon.
   void UpdateIcon();
 
+  // Add strikes for local card migration, to be called on user closing the
+  // promo bubble.
+  void AddStrikesForBubbleClose();
+
   // Weak reference. Will be nullptr if no bubble is currently shown.
   LocalCardMigrationBubble* local_card_migration_bubble_ = nullptr;
 
@@ -84,6 +88,11 @@
   // Boolean to determine if bubble is called from ReshowBubble().
   bool is_reshow_ = false;
 
+  // Boolean to determine if strikes should be added when bubble is closed. They
+  // should be added only once and only if the bubble isn't closed due to
+  // clicking the Continue button.
+  bool should_add_strikes_on_bubble_close_ = true;
+
   base::ObserverList<LocalCardMigrationControllerObserver>::Unchecked
       observer_list_;
 
diff --git a/chrome/browser/ui/autofill/local_card_migration_dialog_controller_impl.cc b/chrome/browser/ui/autofill/local_card_migration_dialog_controller_impl.cc
index cf85ebc..fc7d5fa4 100644
--- a/chrome/browser/ui/autofill/local_card_migration_dialog_controller_impl.cc
+++ b/chrome/browser/ui/autofill/local_card_migration_dialog_controller_impl.cc
@@ -14,6 +14,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "chrome/browser/autofill/strike_database_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/autofill/local_card_migration_dialog.h"
 #include "chrome/browser/ui/autofill/local_card_migration_dialog_factory.h"
 #include "chrome/browser/ui/autofill/local_card_migration_dialog_state.h"
@@ -23,7 +25,9 @@
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
 #include "components/autofill/core/browser/local_card_migration_manager.h"
+#include "components/autofill/core/browser/local_card_migration_strike_database.h"
 #include "components/autofill/core/browser/payments/payments_service_url.h"
+#include "components/autofill/core/browser/strike_database.h"
 #include "components/autofill/core/browser/validation.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -169,13 +173,25 @@
 }
 
 void LocalCardMigrationDialogControllerImpl::OnCancelButtonClicked() {
+  // Add strikes for local card migration due to user closing the main dialog.
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillSaveCreditCardUsesStrikeSystemV2) &&
+      base::FeatureList::IsEnabled(
+          features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
+    LocalCardMigrationStrikeDatabase local_card_migration_strike_database(
+        StrikeDatabaseFactory::GetForProfile(
+            Profile::FromBrowserContext(web_contents()->GetBrowserContext())));
+    local_card_migration_strike_database.AddStrikes(
+        LocalCardMigrationStrikeDatabase::kStrikesToAddWhenDialogClosed);
+  } else {
+    prefs::SetLocalCardMigrationPromptPreviouslyCancelled(pref_service_, true);
+  }
+
   AutofillMetrics::LogLocalCardMigrationDialogUserInteractionMetric(
       dialog_is_visible_duration_timer_.Elapsed(),
       AutofillMetrics::
           LOCAL_CARD_MIGRATION_DIALOG_CLOSED_CANCEL_BUTTON_CLICKED);
 
-  prefs::SetLocalCardMigrationPromptPreviouslyCancelled(pref_service_, true);
-
   start_migrating_cards_callback_.Reset();
   NotifyMigrationNoLongerAvailable();
 }
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index fc2fcef..3af4bdd 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -43,12 +43,11 @@
 
 namespace {
 
-// By spec, dropdowns should have a min width of 64, a max width of 456, and
-// should always have a width which is a multiple of 12.
+// By spec, dropdowns should always have a width which is a multiple of 12.
 constexpr int kAutofillPopupWidthMultiple = 12;
-constexpr int kAutofillPopupMinWidth = 64;
+constexpr int kAutofillPopupMinWidth = kAutofillPopupWidthMultiple * 16;
 // TODO(crbug.com/831603): move handling the max width to the base class.
-constexpr int kAutofillPopupMaxWidth = 456;
+constexpr int kAutofillPopupMaxWidth = kAutofillPopupWidthMultiple * 38;
 
 // Max width for the username and masked password.
 constexpr int kAutofillPopupUsernameMaxWidth = 272;
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
index d745637..d9767ad 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
@@ -764,6 +764,91 @@
   EXPECT_EQ(nullptr, personal_data_->GetCreditCardByNumber(kSecondCardNumber));
 }
 
+// Ensures that rejecting the main migration dialog adds 3 strikes.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClosingDialogAddsLocalCardMigrationStrikes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+  // Click the [Cancel] button, should add and log 3 strikes.
+  ClickOnCancelButton(GetLocalCardMigrationMainDialogView());
+
+  // Metrics
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Autofill.StrikeDatabase.NthStrikeAdded.LocalCardMigration"),
+              ElementsAre(Bucket(3, 1)));
+}
+
+// Ensures that rejecting the migration bubble adds 2 strikes.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClosingBubbleAddsLocalCardMigrationStrikes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  ClickOnDialogViewAndWait(GetCloseButton(),
+                           GetLocalCardMigrationOfferBubbleViews());
+
+  // No bubble should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // Metrics
+  EXPECT_THAT(histogram_tester.GetAllSamples(
+                  "Autofill.StrikeDatabase.NthStrikeAdded.LocalCardMigration"),
+              ElementsAre(Bucket(2, 1)));
+}
+
+// Ensures that reshowing and closing bubble after previously closing it does
+// not add strikes.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ReshowingBubbleDoesNotAddStrikes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  ClickOnDialogViewAndWait(GetCloseButton(),
+                           GetLocalCardMigrationOfferBubbleViews());
+  base::HistogramTester histogram_tester;
+  ClickOnView(GetLocalCardMigrationIconView());
+
+  // Clicking the icon should reshow the bubble.
+  EXPECT_TRUE(
+      FindViewInDialogById(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_BUBBLE,
+                           GetLocalCardMigrationOfferBubbleViews())
+          ->visible());
+
+  ClickOnDialogViewAndWait(GetCloseButton(),
+                           GetLocalCardMigrationOfferBubbleViews());
+
+  // Metrics
+  histogram_tester.ExpectTotalCount(
+      "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
+}
+
 // TODO(crbug.com/897998):
 // - Update test set-up and add navagation tests.
 // - Add more tests for feedback dialog.
diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
index 743ac2f..97bef53 100644
--- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
+++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views.cc
@@ -29,7 +29,6 @@
 #include "services/ws/public/cpp/gpu/gpu.h"  // nogncheck
 #include "services/ws/public/mojom/constants.mojom.h"
 #include "ui/display/screen.h"
-#include "ui/views/mus/mus_client.h"
 #include "ui/views/widget/desktop_aura/desktop_screen.h"
 #include "ui/wm/core/wm_state.h"
 #endif  // defined(USE_AURA)
diff --git a/chrome/browser/ui/views/ime_driver/ime_driver_mus.cc b/chrome/browser/ui/views/ime_driver/ime_driver_mus.cc
index dddf0a8..a7e2d97 100644
--- a/chrome/browser/ui/views/ime_driver/ime_driver_mus.cc
+++ b/chrome/browser/ui/views/ime_driver/ime_driver_mus.cc
@@ -22,17 +22,17 @@
 #include "chrome/browser/ui/views/ime_driver/simple_input_method.h"
 #endif  // defined(OS_CHROMEOS)
 
-IMEDriver::IMEDriver() {
+IMEDriverMus::IMEDriverMus() {
   ui::IMEBridge::Initialize();
 }
 
-IMEDriver::~IMEDriver() {}
+IMEDriverMus::~IMEDriverMus() {}
 
 // static
-void IMEDriver::Register() {
+void IMEDriverMus::Register() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   ws::mojom::IMEDriverPtr ime_driver_ptr;
-  mojo::MakeStrongBinding(std::make_unique<IMEDriver>(),
+  mojo::MakeStrongBinding(std::make_unique<IMEDriverMus>(),
                           MakeRequest(&ime_driver_ptr));
   ws::mojom::IMERegistrarPtr ime_registrar;
   content::ServiceManagerConnection::GetForProcess()
@@ -41,9 +41,10 @@
   ime_registrar->RegisterDriver(std::move(ime_driver_ptr));
 }
 
-void IMEDriver::StartSession(ws::mojom::InputMethodRequest input_method_request,
-                             ws::mojom::TextInputClientPtr client,
-                             ws::mojom::SessionDetailsPtr details) {
+void IMEDriverMus::StartSession(
+    ws::mojom::InputMethodRequest input_method_request,
+    ws::mojom::TextInputClientPtr client,
+    ws::mojom::SessionDetailsPtr details) {
 #if defined(OS_CHROMEOS)
   std::unique_ptr<RemoteTextInputClient> remote_client =
       std::make_unique<RemoteTextInputClient>(std::move(client),
diff --git a/chrome/browser/ui/views/ime_driver/ime_driver_mus.h b/chrome/browser/ui/views/ime_driver/ime_driver_mus.h
index a4bb636..53ed4ba 100644
--- a/chrome/browser/ui/views/ime_driver/ime_driver_mus.h
+++ b/chrome/browser/ui/views/ime_driver/ime_driver_mus.h
@@ -9,10 +9,10 @@
 #include "services/ws/public/mojom/ime/ime.mojom.h"
 
 // Creates an InputMethodBridge when an IME session is started via mojo.
-class IMEDriver : public ws::mojom::IMEDriver {
+class IMEDriverMus : public ws::mojom::IMEDriver {
  public:
-  IMEDriver();
-  ~IMEDriver() override;
+  IMEDriverMus();
+  ~IMEDriverMus() override;
 
   // Instantiate the IME driver and register it to the UI service.
   static void Register();
@@ -23,7 +23,7 @@
                     ws::mojom::TextInputClientPtr client,
                     ws::mojom::SessionDetailsPtr details) override;
 
-  DISALLOW_COPY_AND_ASSIGN(IMEDriver);
+  DISALLOW_COPY_AND_ASSIGN(IMEDriverMus);
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_IME_DRIVER_IME_DRIVER_MUS_H_
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.cc b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
index cc91f56d..9482d29 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.cc
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
@@ -355,13 +355,28 @@
       [](const views::View* view, const views::SizeBounds& maximum_size) {
         const BrowserActionsContainer* browser_actions =
             static_cast<const BrowserActionsContainer*>(view);
-        gfx::Size size = browser_actions->GetPreferredSize();
+        gfx::Size preferred_size = browser_actions->GetPreferredSize();
         if (maximum_size.width()) {
-          size.set_width(
-              browser_actions->GetWidthForMaxWidth(*maximum_size.width()));
+          int width;
+          if (browser_actions->resizing() || browser_actions->animating()) {
+            // When there are actions present, the floor on the size of the
+            // browser actions bar should be the resize handle.
+            const int min_width = browser_actions->num_toolbar_actions() == 0
+                                      ? 0
+                                      : browser_actions->GetResizeAreaWidth();
+            // The ceiling on the value is the lesser of the preferred and
+            // available size.
+            width = std::max(min_width, std::min(preferred_size.width(),
+                                                 *maximum_size.width()));
+          } else {
+            // When not animating or resizing, the desired width should always
+            // be based on the number of icons that can be displayed.
+            width = browser_actions->GetWidthForMaxWidth(*maximum_size.width());
+          }
+          preferred_size =
+              gfx::Size(width, browser_actions->GetHeightForWidth(width));
         }
-        size.set_height(browser_actions->GetHeightForWidth(size.width()));
-        return size;
+        return preferred_size;
       });
 }
 
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.h b/chrome/browser/ui/views/toolbar/browser_actions_container.h
index dbecdff..3f242b77 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.h
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.h
@@ -164,6 +164,9 @@
     return resize_animation_ && resize_animation_->is_animating();
   }
 
+  // Is the view being resized?
+  bool resizing() const { return resize_starting_width_.has_value(); }
+
   // Returns the ID of the action represented by the view at |index|.
   std::string GetIdAt(size_t index) const;
 
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index ea09634..d1d94c4c 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -618,10 +618,6 @@
     return &NewWebUI<ManagementUI>;
 #endif
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  if (url.host_piece() == chrome::kChromeUIExtensionsFrameHost)
-    return &NewWebUI<extensions::ExtensionsUI>;
-#endif
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
   if (url.host_piece() == chrome::kChromeUIPrintHost &&
       !profile->GetPrefs()->GetBoolean(prefs::kPrintPreviewDisabled)) {
@@ -893,8 +889,7 @@
     return ManagementUI::GetFaviconResourceBytes(scale_factor);
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-  if (page_url.host_piece() == chrome::kChromeUIExtensionsHost ||
-      page_url.host_piece() == chrome::kChromeUIExtensionsFrameHost) {
+  if (page_url.host_piece() == chrome::kChromeUIExtensionsHost) {
     return extensions::ExtensionsUI::GetFaviconResourceBytes(scale_factor);
   }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index dc9fbd3..7c5fd013 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -315,9 +315,7 @@
   VLOG(1) << "MaybePreloadAuthExtension";
 
   if (!network_portal_detector_) {
-    NetworkPortalDetectorImpl* detector = new NetworkPortalDetectorImpl(
-        g_browser_process->system_network_context_manager()
-            ->GetURLLoaderFactory());
+    NetworkPortalDetectorImpl* detector = new NetworkPortalDetectorImpl();
     detector->set_portal_test_url(GURL(kRestrictiveProxyURL));
     network_portal_detector_.reset(detector);
     network_portal_detector_->AddObserver(this);
diff --git a/chrome/browser/ui/webui/downloads/downloads_ui.cc b/chrome/browser/ui/webui/downloads/downloads_ui.cc
index 9c32e4e6..9f595a7b 100644
--- a/chrome/browser/ui/webui/downloads/downloads_ui.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_ui.cc
@@ -102,10 +102,8 @@
 
   source->AddLocalizedString("inIncognito", IDS_DOWNLOAD_IN_INCOGNITO);
 
-  source->AddResourcePath("images/1x/incognito_marker.png",
-                          IDR_DOWNLOADS_IMAGES_1X_INCOGNITO_MARKER_PNG);
-  source->AddResourcePath("images/2x/incognito_marker.png",
-                          IDR_DOWNLOADS_IMAGES_2X_INCOGNITO_MARKER_PNG);
+  source->AddResourcePath("images/incognito_marker.svg",
+                          IDR_DOWNLOADS_IMAGES_INCOGNITO_MARKER_SVG);
   source->AddResourcePath("images/no_downloads.svg",
                           IDR_DOWNLOADS_IMAGES_NO_DOWNLOADS_SVG);
   source->AddResourcePath("downloads.mojom-lite.js",
@@ -113,8 +111,7 @@
 
 #if BUILDFLAG(OPTIMIZE_WEBUI)
   source->UseGzip(base::BindRepeating([](const std::string& path) {
-    return path != "images/1x/incognito_marker.png" &&
-           path != "images/2x/incognito_marker.png" &&
+    return path != "images/incognito_marker.svg" &&
            path != "images/no_downloads.svg" &&
            path != "downloads.mojom-lite.js";
   }));
diff --git a/chrome/browser/ui/webui/policy_ui.cc b/chrome/browser/ui/webui/policy_ui.cc
index af8e9a3..5f5fa6f3 100644
--- a/chrome/browser/ui/webui/policy_ui.cc
+++ b/chrome/browser/ui/webui/policy_ui.cc
@@ -54,10 +54,8 @@
   source->AddLocalizedString("labelStatus", IDS_POLICY_LABEL_STATUS);
   source->AddLocalizedString("showUnset", IDS_POLICY_SHOW_UNSET);
   source->AddLocalizedString("noPoliciesSet", IDS_POLICY_NO_POLICIES_SET);
-  source->AddLocalizedString("showExpandedValue",
-                             IDS_POLICY_SHOW_EXPANDED_VALUE);
-  source->AddLocalizedString("hideExpandedValue",
-                             IDS_POLICY_HIDE_EXPANDED_VALUE);
+  source->AddLocalizedString("showMore", IDS_POLICY_SHOW_MORE);
+  source->AddLocalizedString("showLess", IDS_POLICY_SHOW_LESS);
   source->AddLocalizedString("showExpandedStatus",
                              IDS_POLICY_SHOW_EXPANDED_STATUS);
   source->AddLocalizedString("hideExpandedStatus",
diff --git a/chrome/browser/ui/webui/policy_ui_browsertest.cc b/chrome/browser/ui/webui/policy_ui_browsertest.cc
index 9de366e..4493949 100644
--- a/chrome/browser/ui/webui/policy_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/policy_ui_browsertest.cc
@@ -137,10 +137,11 @@
   // Populate expected status.
   if (unknown)
     expected_policy.push_back(
-        l10n_util::GetStringUTF8(IDS_POLICY_LABEL_MESSAGES));
+        l10n_util::GetStringUTF8(IDS_POLICY_HEADER_WARNING));
+  else if (!policy_map_entry)
+    expected_policy.push_back(l10n_util::GetStringUTF8(IDS_POLICY_UNSET));
   else
-    expected_policy.push_back(std::string());
-
+    expected_policy.push_back(l10n_util::GetStringUTF8(IDS_POLICY_OK));
   return expected_policy;
 }
 
@@ -276,7 +277,7 @@
       "  for (var j = 0; j < items.length; ++j) {"
       "    var children = items[j].querySelectorAll('div');"
       "    var values = [];"
-      "    for(var k = 0; k < children.length; ++k) {"
+      "    for(var k = 0; k < children.length - 1; ++k) {"
       "      values.push(children[k].textContent.trim());"
       "    }"
       "    policies.push(values);"
@@ -304,7 +305,8 @@
     for (size_t j = 0; j < expected_policy.size(); ++j) {
       std::string value;
       ASSERT_TRUE(actual_policy->GetString(j, &value));
-      EXPECT_EQ(expected_policy[j], value);
+      if (expected_policy[j] != value)
+        EXPECT_EQ(expected_policy[j], value);
     }
   }
 }
diff --git a/chrome/browser/ui/webui/policy_ui_handler.cc b/chrome/browser/ui/webui/policy_ui_handler.cc
index abf8f77..7c943dc 100644
--- a/chrome/browser/ui/webui/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy_ui_handler.cc
@@ -624,16 +624,19 @@
       content::WebUIDataSource* source) {
   AddLocalizedPolicyStrings(source, policy::kPolicySources,
                             static_cast<size_t>(policy::POLICY_SOURCE_COUNT));
+  source->AddLocalizedString("conflict", IDS_POLICY_LABEL_CONFLICT);
   source->AddLocalizedString("headerLevel", IDS_POLICY_HEADER_LEVEL);
   source->AddLocalizedString("headerName", IDS_POLICY_HEADER_NAME);
   source->AddLocalizedString("headerScope", IDS_POLICY_HEADER_SCOPE);
   source->AddLocalizedString("headerSource", IDS_POLICY_HEADER_SOURCE);
   source->AddLocalizedString("headerStatus", IDS_POLICY_HEADER_STATUS);
   source->AddLocalizedString("headerValue", IDS_POLICY_HEADER_VALUE);
-  source->AddLocalizedString("headerWarning", IDS_POLICY_HEADER_WARNING);
+  source->AddLocalizedString("warning", IDS_POLICY_HEADER_WARNING);
   source->AddLocalizedString("levelMandatory", IDS_POLICY_LEVEL_MANDATORY);
   source->AddLocalizedString("levelRecommended", IDS_POLICY_LEVEL_RECOMMENDED);
   source->AddLocalizedString("messages", IDS_POLICY_LABEL_MESSAGES);
+  source->AddLocalizedString("warningAndConflicts",
+                             IDS_POLICY_LABEL_WARNING_AND_CONFLICT);
   source->AddLocalizedString("notSpecified", IDS_POLICY_NOT_SPECIFIED);
   source->AddLocalizedString("ok", IDS_POLICY_OK);
   source->AddLocalizedString("scopeDevice", IDS_POLICY_SCOPE_DEVICE);
diff --git a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
index 01cb670..c108af6 100644
--- a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
+++ b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
@@ -246,11 +246,15 @@
         checked_other_types);
   }
 
-  // If Sync is running, prevent it from being paused during the operation.
-  // However, if Sync is in error, clearing cookies should pause it.
   std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion>
       scoped_data_deletion;
-  if (sync_ui_util::GetStatus(profile_) == sync_ui_util::SYNCED) {
+
+  // If Sync is running, prevent it from being paused during the operation.
+  // However, if Sync is in error, clearing cookies should pause it.
+  if (!profile_->IsGuestSession() &&
+      sync_ui_util::GetStatus(profile_) == sync_ui_util::SYNCED) {
+    // Settings can not be opened in incognito windows.
+    DCHECK(!profile_->IsOffTheRecord());
     scoped_data_deletion = AccountReconcilorFactory::GetForProfile(profile_)
                                ->GetScopedSyncDataDeletion();
   }
diff --git a/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.cc b/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.cc
new file mode 100644
index 0000000..658de1d
--- /dev/null
+++ b/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.cc
@@ -0,0 +1,83 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.h"
+
+#include <utility>
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/net/system_network_context_manager.h"
+#include "chrome/browser/search/background/onboarding_ntp_backgrounds.h"
+#include "net/base/load_flags.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "url/gurl.h"
+
+namespace nux {
+
+NtpBackgroundFetcher::NtpBackgroundFetcher(
+    size_t index,
+    const content::WebUIDataSource::GotDataCallback& callback)
+    : index_(index), callback_(callback) {
+  DCHECK(callback_ && !callback_.is_null());
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("nux_ntp_background_preview", R"(
+        semantics {
+          sender: "Navi Onboarding NTP background module"
+          description:
+            "As part of the Navi Onboarding flow, the NTP background module "
+            "allows users to preview what a custom background for the "
+            "New Tab Page would look like. The list of available backgrounds "
+            "is manually whitelisted."
+          trigger:
+            "The user selects an image to preview."
+          data:
+            "User-selected image URL."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "This feature cannot be disabled by settings, but it is only "
+            "triggered by a user action."
+          policy_exception_justification: "Not implemented."
+        })");
+
+  auto backgrounds = GetOnboardingNtpBackgrounds();
+
+  if (index_ >= backgrounds.size()) {
+    OnFetchCompleted(nullptr);
+    return;
+  }
+
+  GURL url = backgrounds[index_];
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  resource_request->load_flags =
+      net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES;
+  simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+                                                    traffic_annotation);
+
+  network::mojom::URLLoaderFactory* loader_factory =
+      g_browser_process->system_network_context_manager()
+          ->GetURLLoaderFactory();
+  simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      loader_factory, base::BindOnce(&NtpBackgroundFetcher::OnFetchCompleted,
+                                     base::Unretained(this)));
+}
+
+NtpBackgroundFetcher::~NtpBackgroundFetcher() = default;
+
+void NtpBackgroundFetcher::OnFetchCompleted(
+    std::unique_ptr<std::string> response_body) {
+  if (response_body) {
+    callback_.Run(base::RefCountedString::TakeString(response_body.release()));
+  } else {
+    callback_.Run(base::MakeRefCounted<base::RefCountedBytes>());
+  }
+}
+
+}  // namespace nux
diff --git a/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.h b/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.h
new file mode 100644
index 0000000..1e4892a
--- /dev/null
+++ b/chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.h
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_NTP_BACKGROUND_FETCHER_H_
+#define CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_NTP_BACKGROUND_FETCHER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace network {
+class SimpleURLLoader;
+}
+
+namespace nux {
+
+class NtpBackgroundFetcher {
+ public:
+  NtpBackgroundFetcher(
+      size_t index,
+      const content::WebUIDataSource::GotDataCallback& callback);
+  ~NtpBackgroundFetcher();
+
+ private:
+  void OnFetchCompleted(std::unique_ptr<std::string> response_body);
+
+  size_t index_;
+  content::WebUIDataSource::GotDataCallback callback_;
+  std::unique_ptr<network::SimpleURLLoader> simple_loader_;
+
+  DISALLOW_COPY_AND_ASSIGN(NtpBackgroundFetcher);
+};
+
+}  // namespace nux
+
+#endif  // CHROME_BROWSER_UI_WEBUI_WELCOME_NUX_NTP_BACKGROUND_FETCHER_H_
diff --git a/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc b/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
index 433b9612..dab7ae1 100644
--- a/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
+++ b/chrome/browser/ui/webui/welcome/nux/ntp_background_handler.cc
@@ -54,6 +54,7 @@
   base::ListValue list_value;
   std::array<GURL, kOnboardingNtpBackgroundsCount> onboardingNtpBackgrounds =
       GetOnboardingNtpBackgrounds();
+  const std::string kUrlPrefix = "preview-background.jpg?";
 
   auto element = std::make_unique<base::DictionaryValue>();
   int id = static_cast<int>(NtpBackgrounds::kEarth);
@@ -61,7 +62,7 @@
   element->SetString("title",
                      l10n_util::GetStringUTF8(
                          IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_EARTH_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("imageUrl", kUrlPrefix + std::to_string(id));
   element->SetString("thumbnailClass", "earth");
   list_value.Append(std::move(element));
 
@@ -71,7 +72,7 @@
   element->SetString(
       "title", l10n_util::GetStringUTF8(
                    IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_CITYSCAPE_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("imageUrl", kUrlPrefix + std::to_string(id));
   element->SetString("thumbnailClass", "cityscape");
   list_value.Append(std::move(element));
 
@@ -81,7 +82,7 @@
   element->SetString(
       "title", l10n_util::GetStringUTF8(
                    IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_LANDSCAPE_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("imageUrl", kUrlPrefix + std::to_string(id));
   element->SetString("thumbnailClass", "landscape");
   list_value.Append(std::move(element));
 
@@ -91,7 +92,7 @@
   element->SetString("title",
                      l10n_util::GetStringUTF8(
                          IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_ART_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("imageUrl", kUrlPrefix + std::to_string(id));
   element->SetString("thumbnailClass", "art");
   list_value.Append(std::move(element));
 
@@ -102,7 +103,7 @@
       "title",
       l10n_util::GetStringUTF8(
           IDS_ONBOARDING_WELCOME_NTP_BACKGROUND_GEOMETRIC_SHAPES_TITLE));
-  element->SetString("imageUrl", onboardingNtpBackgrounds[id].spec());
+  element->SetString("imageUrl", kUrlPrefix + std::to_string(id));
   element->SetString("thumbnailClass", "geometric-shapes");
   list_value.Append(std::move(element));
 
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index 34534c1..76702dc 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -4,9 +4,6 @@
 
 #include "chrome/browser/ui/webui/welcome/welcome_ui.h"
 
-#include <memory>
-#include <string>
-
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
@@ -30,7 +27,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_ui_data_source.h"
 #include "net/base/url_util.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -47,6 +43,30 @@
 #endif
 }  // namespace
 
+bool HandleRequestCallback(
+    base::WeakPtr<WelcomeUI> weak_ptr,
+    const std::string& path,
+    const content::WebUIDataSource::GotDataCallback& callback) {
+  if (!base::StartsWith(path, "preview-background.jpg",
+                        base::CompareCase::SENSITIVE)) {
+    return false;
+  }
+
+  std::string index_param = path.substr(path.find_first_of("?") + 1);
+  int background_index = -1;
+  if (!base::StringToInt(index_param, &background_index) ||
+      background_index < 0) {
+    return false;
+  }
+
+  if (weak_ptr) {
+    weak_ptr->CreateBackgroundFetcher(background_index, callback);
+    return true;
+  }
+
+  return false;
+}
+
 void AddOnboardingStrings(content::WebUIDataSource* html_source) {
   static constexpr LocalizedString kLocalizedStrings[] = {
       // Shared strings.
@@ -105,7 +125,7 @@
 }
 
 WelcomeUI::WelcomeUI(content::WebUI* web_ui, const GURL& url)
-    : content::WebUIController(web_ui) {
+    : content::WebUIController(web_ui), weak_ptr_factory_(this) {
   Profile* profile = Profile::FromWebUI(web_ui);
 
   // This page is not shown to incognito or guest profiles. If one should end up
@@ -184,6 +204,8 @@
                             nux::GetNuxOnboardingModules(profile)
                                 .FindKey("show-email-interstitial")
                                 ->GetBool());
+    html_source->SetRequestFilter(base::BindRepeating(
+        &HandleRequestCallback, weak_ptr_factory_.GetWeakPtr()));
   } else if (kIsBranded && is_dice) {
     // Use special layout if the application is branded and DICE is enabled.
     html_source->AddLocalizedString("headerText", IDS_WELCOME_HEADER);
@@ -234,6 +256,13 @@
 
 WelcomeUI::~WelcomeUI() {}
 
+void WelcomeUI::CreateBackgroundFetcher(
+    size_t background_index,
+    const content::WebUIDataSource::GotDataCallback& callback) {
+  background_fetcher_ =
+      std::make_unique<nux::NtpBackgroundFetcher>(background_index, callback);
+}
+
 void WelcomeUI::StorePageSeen(Profile* profile) {
   // Store that this profile has been shown the Welcome page.
   profile->GetPrefs()->SetBoolean(prefs::kHasSeenWelcomePage, true);
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.h b/chrome/browser/ui/webui/welcome/welcome_ui.h
index a352818..e74a6f9 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.h
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.h
@@ -5,8 +5,14 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_WELCOME_WELCOME_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_WELCOME_WELCOME_UI_H_
 
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.h"
 #include "content/public/browser/web_ui_controller.h"
+#include "content/public/browser/web_ui_data_source.h"
 #include "url/gurl.h"
 
 // The WebUI for chrome://welcome, the page which greets new Desktop users and
@@ -18,8 +24,14 @@
   WelcomeUI(content::WebUI* web_ui, const GURL& url);
   ~WelcomeUI() override;
 
+  void CreateBackgroundFetcher(
+      size_t background_index,
+      const content::WebUIDataSource::GotDataCallback& callback);
+
  private:
   void StorePageSeen(Profile* profile);
+  std::unique_ptr<nux::NtpBackgroundFetcher> background_fetcher_;
+  base::WeakPtrFactory<WelcomeUI> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(WelcomeUI);
 };
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index cb41098..69c29b3 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -652,8 +652,8 @@
 
     # Common infrastructure.
     sources = [
-      "test/mock_openvr_device_hook_base.cc",
-      "test/mock_openvr_device_hook_base.h",
+      "test/mock_xr_device_hook_base.cc",
+      "test/mock_xr_device_hook_base.h",
       "test/webvr_browser_test.cc",
       "test/webvr_browser_test.h",
       "test/webxr_browser_test.cc",
diff --git a/chrome/browser/vr/DEPS b/chrome/browser/vr/DEPS
index d6ce7b3..af8f689 100644
--- a/chrome/browser/vr/DEPS
+++ b/chrome/browser/vr/DEPS
@@ -8,7 +8,7 @@
 
 specific_include_rules = {
   ".*test.*\.cc": [
-    "+device/vr/openvr/test/test_hook.h",
+    "+device/vr/test/test_hook.h",
     "+third_party/openvr/src/headers/openvr.h",
   ],
 }
\ No newline at end of file
diff --git a/chrome/browser/vr/test/DEPS b/chrome/browser/vr/test/DEPS
index 8896d4de..d194eb2 100644
--- a/chrome/browser/vr/test/DEPS
+++ b/chrome/browser/vr/test/DEPS
@@ -1,5 +1,5 @@
 include_rules = [
   "+device/vr/openvr/openvr_device_provider.h",
-  "+device/vr/openvr/test/test_hook.h",
+  "+device/vr/test/test_hook.h",
   "+mojo/core/embedder",
 ]
diff --git a/chrome/browser/vr/test/mock_openvr_device_hook_base.cc b/chrome/browser/vr/test/mock_xr_device_hook_base.cc
similarity index 88%
rename from chrome/browser/vr/test/mock_openvr_device_hook_base.cc
rename to chrome/browser/vr/test/mock_xr_device_hook_base.cc
index 1a79452..87db8cb 100644
--- a/chrome/browser/vr/test/mock_openvr_device_hook_base.cc
+++ b/chrome/browser/vr/test/mock_xr_device_hook_base.cc
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/vr/test/mock_openvr_device_hook_base.h"
+#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
 #include "content/public/common/service_manager_connection.h"
-#include "device/vr/openvr/test/test_hook.h"
 #include "device/vr/public/mojom/isolated_xr_service.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -49,7 +48,7 @@
   return ret;
 }
 
-MockOpenVRDeviceHookBase::MockOpenVRDeviceHookBase()
+MockXRDeviceHookBase::MockXRDeviceHookBase()
     : tracked_classes_{device_test::mojom::TrackedDeviceClass::
                            kTrackedDeviceInvalid},
       binding_(this) {
@@ -69,11 +68,11 @@
   test_hook_registration_->SetTestHook(std::move(client));
 }
 
-MockOpenVRDeviceHookBase::~MockOpenVRDeviceHookBase() {
+MockXRDeviceHookBase::~MockXRDeviceHookBase() {
   StopHooking();
 }
 
-void MockOpenVRDeviceHookBase::StopHooking() {
+void MockXRDeviceHookBase::StopHooking() {
   // We don't call test_hook_registration_->SetTestHook(nullptr), since that
   // will potentially deadlock with reentrant or crossing synchronous mojo
   // calls.
@@ -81,13 +80,13 @@
   test_hook_registration_ = nullptr;
 }
 
-void MockOpenVRDeviceHookBase::OnFrameSubmitted(
+void MockXRDeviceHookBase::OnFrameSubmitted(
     device_test::mojom::SubmittedFrameDataPtr frame_data,
     device_test::mojom::XRTestHook::OnFrameSubmittedCallback callback) {
   std::move(callback).Run();
 }
 
-void MockOpenVRDeviceHookBase::WaitGetDeviceConfig(
+void MockXRDeviceHookBase::WaitGetDeviceConfig(
     device_test::mojom::XRTestHook::WaitGetDeviceConfigCallback callback) {
   device_test::mojom::DeviceConfigPtr ret =
       device_test::mojom::DeviceConfig::New();
@@ -97,21 +96,21 @@
   std::move(callback).Run(std::move(ret));
 }
 
-void MockOpenVRDeviceHookBase::WaitGetPresentingPose(
+void MockXRDeviceHookBase::WaitGetPresentingPose(
     device_test::mojom::XRTestHook::WaitGetPresentingPoseCallback callback) {
   auto pose = device_test::mojom::PoseFrameData::New();
   pose->device_to_origin = gfx::Transform();
   std::move(callback).Run(std::move(pose));
 }
 
-void MockOpenVRDeviceHookBase::WaitGetMagicWindowPose(
+void MockXRDeviceHookBase::WaitGetMagicWindowPose(
     device_test::mojom::XRTestHook::WaitGetMagicWindowPoseCallback callback) {
   auto pose = device_test::mojom::PoseFrameData::New();
   pose->device_to_origin = gfx::Transform();
   std::move(callback).Run(std::move(pose));
 }
 
-void MockOpenVRDeviceHookBase::WaitGetControllerRoleForTrackedDeviceIndex(
+void MockXRDeviceHookBase::WaitGetControllerRoleForTrackedDeviceIndex(
     unsigned int index,
     device_test::mojom::XRTestHook::
         WaitGetControllerRoleForTrackedDeviceIndexCallback callback) {
@@ -122,7 +121,7 @@
   std::move(callback).Run(role);
 }
 
-void MockOpenVRDeviceHookBase::WaitGetTrackedDeviceClass(
+void MockXRDeviceHookBase::WaitGetTrackedDeviceClass(
     unsigned int index,
     device_test::mojom::XRTestHook::WaitGetTrackedDeviceClassCallback
         callback) {
@@ -130,7 +129,7 @@
   std::move(callback).Run(tracked_classes_[index]);
 }
 
-void MockOpenVRDeviceHookBase::WaitGetControllerData(
+void MockXRDeviceHookBase::WaitGetControllerData(
     unsigned int index,
     device_test::mojom::XRTestHook::WaitGetControllerDataCallback callback) {
   if (tracked_classes_[index] ==
@@ -148,7 +147,7 @@
   std::move(callback).Run(DeviceToMojoControllerFrameData(data));
 }
 
-unsigned int MockOpenVRDeviceHookBase::ConnectController(
+unsigned int MockXRDeviceHookBase::ConnectController(
     const device::ControllerFrameData& initial_data) {
   // Find the first open tracked device slot and fill that.
   for (unsigned int i = 0; i < device::kMaxTrackedDevices; ++i) {
@@ -167,7 +166,7 @@
   return device::kMaxTrackedDevices;
 }
 
-void MockOpenVRDeviceHookBase::UpdateController(
+void MockXRDeviceHookBase::UpdateController(
     unsigned int index,
     const device::ControllerFrameData& updated_data) {
   auto iter = controller_data_map_.find(index);
@@ -175,7 +174,7 @@
   iter->second = updated_data;
 }
 
-void MockOpenVRDeviceHookBase::DisconnectController(unsigned int index) {
+void MockXRDeviceHookBase::DisconnectController(unsigned int index) {
   DCHECK(tracked_classes_[index] ==
          device_test::mojom::TrackedDeviceClass::kTrackedDeviceController);
   auto iter = controller_data_map_.find(index);
@@ -185,7 +184,7 @@
       device_test::mojom::TrackedDeviceClass::kTrackedDeviceInvalid;
 }
 
-device::ControllerFrameData MockOpenVRDeviceHookBase::CreateValidController(
+device::ControllerFrameData MockXRDeviceHookBase::CreateValidController(
     device::ControllerRole role) {
   device::ControllerFrameData ret;
   // Because why shouldn't a 64 button controller exist?
diff --git a/chrome/browser/vr/test/mock_openvr_device_hook_base.h b/chrome/browser/vr/test/mock_xr_device_hook_base.h
similarity index 84%
rename from chrome/browser/vr/test/mock_openvr_device_hook_base.h
rename to chrome/browser/vr/test/mock_xr_device_hook_base.h
index e8573f1..2ec7c05b 100644
--- a/chrome/browser/vr/test/mock_openvr_device_hook_base.h
+++ b/chrome/browser/vr/test/mock_xr_device_hook_base.h
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_VR_TEST_MOCK_OPENVR_DEVICE_HOOK_BASE_H_
-#define CHROME_BROWSER_VR_TEST_MOCK_OPENVR_DEVICE_HOOK_BASE_H_
+#ifndef CHROME_BROWSER_VR_TEST_MOCK_XR_DEVICE_HOOK_BASE_H_
+#define CHROME_BROWSER_VR_TEST_MOCK_XR_DEVICE_HOOK_BASE_H_
 
 #include "base/containers/flat_map.h"
-#include "device/vr/openvr/test/test_hook.h"
 #include "device/vr/public/mojom/browser_test_interfaces.mojom.h"
+#include "device/vr/test/test_hook.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
-class MockOpenVRDeviceHookBase : public device_test::mojom::XRTestHook {
+class MockXRDeviceHookBase : public device_test::mojom::XRTestHook {
  public:
-  MockOpenVRDeviceHookBase();
-  ~MockOpenVRDeviceHookBase() override;
+  MockXRDeviceHookBase();
+  ~MockXRDeviceHookBase() override;
 
   // device_test::mojom::XRTestHook
   void OnFrameSubmitted(device_test::mojom::SubmittedFrameDataPtr frame_data,
@@ -41,7 +41,7 @@
       device_test::mojom::XRTestHook::WaitGetControllerDataCallback callback)
       override;
 
-  // MockOpenVRDeviceHookBase
+  // MockXRDeviceHookBase
   unsigned int ConnectController(
       const device::ControllerFrameData& initial_data);
   void UpdateController(unsigned int index,
@@ -62,4 +62,4 @@
   device_test::mojom::XRTestHookRegistrationPtr test_hook_registration_;
 };
 
-#endif  // CHROME_BROWSER_VR_TEST_MOCK_OPENVR_DEVICE_HOOK_BASE_H_
+#endif  // CHROME_BROWSER_VR_TEST_MOCK_XR_DEVICE_HOOK_BASE_H_
diff --git a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
index 70f20d9..e7f3e5984 100644
--- a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
@@ -5,7 +5,7 @@
 #include "base/environment.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
-#include "chrome/browser/vr/test/mock_openvr_device_hook_base.h"
+#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
 
 #include <memory>
@@ -20,7 +20,7 @@
   device_test::mojom::DeviceConfigPtr config;
 };
 
-class MyOpenVRMock : public MockOpenVRDeviceHookBase {
+class MyXRMock : public MockXRDeviceHookBase {
  public:
   void OnFrameSubmitted(
       device_test::mojom::SubmittedFrameDataPtr frame_data,
@@ -80,7 +80,7 @@
   return frame_id;
 }
 
-void MyOpenVRMock::OnFrameSubmitted(
+void MyXRMock::OnFrameSubmitted(
     device_test::mojom::SubmittedFrameDataPtr frame_data,
     device_test::mojom::XRTestHook::OnFrameSubmittedCallback callback) {
   unsigned int frame_id = ParseColorFrameId(frame_data->color);
@@ -108,7 +108,7 @@
   std::move(callback).Run();
 }
 
-void MyOpenVRMock::WaitGetMagicWindowPose(
+void MyXRMock::WaitGetMagicWindowPose(
     device_test::mojom::XRTestHook::WaitGetMagicWindowPoseCallback callback) {
   auto pose = device_test::mojom::PoseFrameData::New();
 
@@ -119,7 +119,7 @@
   std::move(callback).Run(std::move(pose));
 }
 
-void MyOpenVRMock::WaitGetPresentingPose(
+void MyXRMock::WaitGetPresentingPose(
     device_test::mojom::XRTestHook::WaitGetPresentingPoseCallback callback) {
   DLOG(ERROR) << "WaitGetPresentingPose: " << frame_id_;
 
@@ -161,7 +161,7 @@
 // out. Validates that submitted frames used expected pose.
 void TestPresentationPosesImpl(WebXrVrBrowserTestBase* t,
                                std::string filename) {
-  MyOpenVRMock my_mock;
+  MyXRMock my_mock;
 
   // Load the test page, and enter presentation.
   t->LoadUrlAndAwaitInitialization(t->GetFileUrlForHtmlTestFile(filename));
@@ -178,8 +178,8 @@
   // Exit presentation.
   t->EndSessionOrFail();
 
-  // Stop hooking OpenVR, so we can safely analyze our cached data without
-  // incoming calls (there may be leftover mojo messages queued).
+  // Stop hooking the VR runtime so we can safely analyze our cached data
+  // without incoming calls (there may be leftover mojo messages queued).
   my_mock.StopHooking();
 
   // Analyze the submitted frames - check for a few things:
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index b13559a..5fb1cecb 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -3,10 +3,9 @@
 // found in the LICENSE file.
 
 #include "base/run_loop.h"
-#include "chrome/browser/vr/test/mock_openvr_device_hook_base.h"
+#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
 #include "chrome/browser/vr/test/webvr_browser_test.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
-#include "device/vr/openvr/test/test_hook.h"
 #include "device/vr/public/mojom/browser_test_interfaces.mojom.h"
 #include "third_party/openvr/src/headers/openvr.h"
 
@@ -33,7 +32,7 @@
   TestPresentationLocksFocusImpl(this, "webxr_test_presentation_locks_focus");
 }
 
-class WebXrControllerInputOpenVRMock : public MockOpenVRDeviceHookBase {
+class WebXrControllerInputMock : public MockXRDeviceHookBase {
  public:
   void OnFrameSubmitted(
       device_test::mojom::SubmittedFrameDataPtr frame_data,
@@ -73,7 +72,7 @@
   unsigned int target_submitted_frames_ = 0;
 };
 
-void WebXrControllerInputOpenVRMock::OnFrameSubmitted(
+void WebXrControllerInputMock::OnFrameSubmitted(
     device_test::mojom::SubmittedFrameDataPtr frame_data,
     device_test::mojom::XRTestHook::OnFrameSubmittedCallback callback) {
   num_submitted_frames_++;
@@ -88,7 +87,7 @@
 // WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
 IN_PROC_BROWSER_TEST_F(WebXrVrBrowserTestStandard,
                        TestControllerInputRegistered) {
-  WebXrControllerInputOpenVRMock my_mock;
+  WebXrControllerInputMock my_mock;
 
   // Connect a controller.
   auto controller_data = my_mock.CreateValidController(
@@ -120,7 +119,7 @@
 // WebXrVrInputTest#testControllerClicksRegisteredOnDaydream
 IN_PROC_BROWSER_TEST_F(WebVrBrowserTestStandard,
                        TestControllerInputRegistered) {
-  WebXrControllerInputOpenVRMock my_mock;
+  WebXrControllerInputMock my_mock;
 
   // Connect a controller.
   auto controller_data = my_mock.CreateValidController(
diff --git a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
index 048cf112..4fdf729 100644
--- a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
@@ -7,7 +7,7 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
-#include "chrome/browser/vr/test/mock_openvr_device_hook_base.h"
+#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
 #include "chrome/browser/vr/test/webvr_browser_test.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
 
@@ -15,7 +15,7 @@
 
 namespace vr {
 
-class MyOpenVRMock : public MockOpenVRDeviceHookBase {
+class MyXRMock : public MockXRDeviceHookBase {
  public:
   void OnFrameSubmitted(
       device_test::mojom::SubmittedFrameDataPtr frame_data,
@@ -39,7 +39,7 @@
   base::RunLoop* wait_loop_ = nullptr;
 };
 
-void MyOpenVRMock::OnFrameSubmitted(
+void MyXRMock::OnFrameSubmitted(
     device_test::mojom::SubmittedFrameDataPtr frame_data,
     device_test::mojom::XRTestHook::OnFrameSubmittedCallback callback) {
   last_submitted_color_ = std::move(frame_data->color);
@@ -56,7 +56,7 @@
 // out. Validates that a pixel was rendered with the expected color.
 void TestPresentationPixelsImpl(WebXrVrBrowserTestBase* t,
                                 std::string filename) {
-  MyOpenVRMock my_mock;
+  MyXRMock my_mock;
 
   // Load the test page, and enter presentation.
   t->LoadUrlAndAwaitInitialization(t->GetFileUrlForHtmlTestFile(filename));
diff --git a/chrome/browser/web_applications/components/install_finalizer.h b/chrome/browser/web_applications/components/install_finalizer.h
index 8ac439a..c02209f82 100644
--- a/chrome/browser/web_applications/components/install_finalizer.h
+++ b/chrome/browser/web_applications/components/install_finalizer.h
@@ -41,6 +41,7 @@
   virtual bool CanPinAppToShelf() const = 0;
   virtual void PinAppToShelf(const AppId& app_id) = 0;
 
+  virtual bool CanReparentTab(bool shortcut_created) const = 0;
   virtual void ReparentTab(const AppId& app_id,
                            content::WebContents* web_contents) = 0;
 
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
index 4b278eda..ea86f01 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
@@ -115,6 +115,10 @@
   BookmarkAppPinToShelf(app);
 }
 
+bool BookmarkAppInstallFinalizer::CanReparentTab(bool shortcut_created) const {
+  return CanBookmarkAppReparentTab(shortcut_created);
+}
+
 void BookmarkAppInstallFinalizer::ReparentTab(
     const web_app::AppId& app_id,
     content::WebContents* web_contents) {
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
index d0c05ad..ba1bd85 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.h
@@ -41,6 +41,7 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const web_app::AppId& app_id) override;
+  bool CanReparentTab(bool shortcut_created) const override;
   void ReparentTab(const web_app::AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.cc b/chrome/browser/web_applications/test/test_install_finalizer.cc
index f552909..eedfa1f2 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.cc
+++ b/chrome/browser/web_applications/test/test_install_finalizer.cc
@@ -61,6 +61,10 @@
   ++num_pin_app_to_shelf_calls_;
 }
 
+bool TestInstallFinalizer::CanReparentTab(bool shortcut_created) const {
+  return true;
+}
+
 void TestInstallFinalizer::ReparentTab(const AppId& app_id,
                                        content::WebContents* web_contents) {
   ++num_reparent_tab_calls_;
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.h b/chrome/browser/web_applications/test/test_install_finalizer.h
index 7457066..7725c92 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.h
+++ b/chrome/browser/web_applications/test/test_install_finalizer.h
@@ -28,6 +28,7 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const AppId& app_id) override;
+  bool CanReparentTab(bool shortcut_created) const override;
   void ReparentTab(const AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 02a4c92..93bd380 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -123,6 +123,12 @@
   NOTIMPLEMENTED();
 }
 
+bool WebAppInstallFinalizer::CanReparentTab(bool shortcut_created) const {
+  // TODO(loyso): Implement it.
+  NOTIMPLEMENTED();
+  return true;
+}
+
 void WebAppInstallFinalizer::ReparentTab(const AppId& app_id,
                                          content::WebContents* web_contents) {
   // TODO(loyso): Implement it.
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index 323b1a9..12ec5c8 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -33,6 +33,7 @@
                          CreateOsShortcutsCallback callback) override;
   bool CanPinAppToShelf() const override;
   void PinAppToShelf(const AppId& app_id) override;
+  bool CanReparentTab(bool shortcut_created) const override;
   void ReparentTab(const AppId& app_id,
                    content::WebContents* web_contents) override;
   bool CanRevealAppShim() const override;
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index 88716438..023640c8 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -214,27 +214,45 @@
   if (InstallInterrupted())
     return;
 
-  if (code == InstallResultCode::kSuccess) {
-    RecordAppBanner(web_contents(), web_app_info->app_url);
-
-    // TODO(loyso): Implement |create_shortcuts| to skip OS shortcuts creation.
-    // TODO(https://crbug.com/915571): Only re-parent tabs upon successful
-    // shortcut creation (at least on macOS).
-    if (install_finalizer_->CanCreateOsShortcuts())
-      install_finalizer_->CreateOsShortcuts(app_id, base::DoNothing());
-
-    if (install_finalizer_->CanPinAppToShelf())
-      install_finalizer_->PinAppToShelf(app_id);
-
-    // TODO(loyso): Implement |reparent_tab| to skip tab reparenting logic.
-    if (web_app_info->open_as_window)
-      install_finalizer_->ReparentTab(app_id, web_contents());
-
-    if (install_finalizer_->CanRevealAppShim())
-      install_finalizer_->RevealAppShim(app_id);
+  if (code != InstallResultCode::kSuccess) {
+    CallInstallCallback(app_id, code);
+    return;
   }
 
-  CallInstallCallback(app_id, code);
+  RecordAppBanner(web_contents(), web_app_info->app_url);
+
+  // TODO(loyso): Implement |create_shortcuts| to skip OS shortcuts creation.
+  auto create_shortcuts_callback = base::BindOnce(
+      &WebAppInstallManager::OnShortcutsCreated, weak_ptr_factory_.GetWeakPtr(),
+      std::move(web_app_info), app_id);
+  if (install_finalizer_->CanCreateOsShortcuts()) {
+    install_finalizer_->CreateOsShortcuts(app_id,
+                                          std::move(create_shortcuts_callback));
+  } else {
+    std::move(create_shortcuts_callback).Run(false /* created_shortcuts */);
+  }
+}
+
+void WebAppInstallManager::OnShortcutsCreated(
+    std::unique_ptr<WebApplicationInfo> web_app_info,
+    const AppId& app_id,
+    bool shortcut_created) {
+  if (InstallInterrupted())
+    return;
+
+  if (install_finalizer_->CanPinAppToShelf())
+    install_finalizer_->PinAppToShelf(app_id);
+
+  // TODO(loyso): Implement |reparent_tab| to skip tab reparenting logic.
+  if (web_app_info->open_as_window &&
+      install_finalizer_->CanReparentTab(shortcut_created)) {
+    install_finalizer_->ReparentTab(app_id, web_contents());
+  }
+
+  if (install_finalizer_->CanRevealAppShim())
+    install_finalizer_->RevealAppShim(app_id);
+
+  CallInstallCallback(app_id, InstallResultCode::kSuccess);
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_install_manager.h b/chrome/browser/web_applications/web_app_install_manager.h
index 4dbf0fe..20d2b42 100644
--- a/chrome/browser/web_applications/web_app_install_manager.h
+++ b/chrome/browser/web_applications/web_app_install_manager.h
@@ -79,6 +79,9 @@
   void OnInstallFinalized(std::unique_ptr<WebApplicationInfo> web_app_info,
                           const AppId& app_id,
                           InstallResultCode code);
+  void OnShortcutsCreated(std::unique_ptr<WebApplicationInfo> web_app_info,
+                          const AppId& app_id,
+                          bool shortcut_created);
 
   // TODO(loyso): Extract these parameters as a struct and reset it on every
   // installation task:
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index ef080783..ee0e2924 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -160,7 +160,9 @@
 
   callback GetPrimaryDisplayScaleFactorCallback = void (double scaleFactor);
 
-  callback IsTabletModeEnabledCallback = void (boolean tabletMode);
+  callback IsTabletModeEnabledCallback = void (boolean enabled);
+
+  callback SetTabletModeEnabledCallback = void(boolean enabled);
 
   callback VoidCallback = void ();
 
@@ -333,7 +335,15 @@
         GetPrimaryDisplayScaleFactorCallback callback);
 
     // Returns the tablet mode enabled status.
-    // |callback| is invoked with the table mode enablement status.
+    // |callback| is invoked with the tablet mode enablement status.
     static void isTabletModeEnabled(IsTabletModeEnabledCallback callback);
+
+    // Enable/disable tablet mode. After calling this function, it won't be
+    // possible to physically switch to/from tablet mode since that
+    // functionality will be disabled.
+    // |enabled|: if set, enable tablet mode.
+    // |callback|: Called when the operation has completed.
+    static void setTabletModeEnabled(boolean enabled,
+        SetTabletModeEnabledCallback callback);
   };
 };
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index a41323a..49aa05a 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -61,8 +61,6 @@
 const char kChromeUIDriveInternalsHost[] = "drive-internals";
 const char kChromeUIExtensionIconHost[] = "extension-icon";
 const char kChromeUIExtensionIconURL[] = "chrome://extension-icon/";
-const char kChromeUIExtensionsFrameHost[] = "extensions-frame";
-const char kChromeUIExtensionsFrameURL[] = "chrome://extensions-frame/";
 const char kChromeUIExtensionsHost[] = "extensions";
 const char kChromeUIExtensionsInternalsHost[] = "extensions-internals";
 const char kChromeUIExtensionsURL[] = "chrome://extensions/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 17d02566..f5eac7a 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -66,8 +66,6 @@
 extern const char kChromeUIDriveInternalsHost[];
 extern const char kChromeUIExtensionIconHost[];
 extern const char kChromeUIExtensionIconURL[];
-extern const char kChromeUIExtensionsFrameHost[];
-extern const char kChromeUIExtensionsFrameURL[];
 extern const char kChromeUIExtensionsHost[];
 extern const char kChromeUIExtensionsInternalsHost[];
 extern const char kChromeUIExtensionsURL[];
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index e885df2..b1fc9d2 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -1905,15 +1905,11 @@
     case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
       message_id = IDS_AX_IMAGE_ANNOTATION_PENDING;
       break;
-    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
-      message_id = IDS_AX_IMAGE_ANNOTATION_EMPTY;
-      break;
     case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
       message_id = IDS_AX_IMAGE_ANNOTATION_ADULT;
       break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
     case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
-      message_id = IDS_AX_IMAGE_ANNOTATION_PROCESS_FAILED;
-      break;
     case ax::mojom::ImageAnnotationStatus::kNone:
     case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
     case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
diff --git a/chrome/services/isolated_xr_device/xr_test_hook_wrapper.h b/chrome/services/isolated_xr_device/xr_test_hook_wrapper.h
index a615a13..e21c8e7 100644
--- a/chrome/services/isolated_xr_device/xr_test_hook_wrapper.h
+++ b/chrome/services/isolated_xr_device/xr_test_hook_wrapper.h
@@ -5,18 +5,19 @@
 #ifndef CHROME_SERVICES_ISOLATED_XR_DEVICE_XR_TEST_HOOK_WRAPPER_H_
 #define CHROME_SERVICES_ISOLATED_XR_DEVICE_XR_TEST_HOOK_WRAPPER_H_
 
-#include "device/vr/openvr/test/test_hook.h"
 #include "device/vr/public/mojom/browser_test_interfaces.mojom.h"
+#include "device/vr/test/test_hook.h"
 
 namespace device {
 
-// Wraps a mojo test hook to implement the OpenVRTestHook C++ interface.  Our
-// use of OpenVR is single-threaded at a time, and we initialize/uninitialize
+// Wraps a mojo test hook to implement the VRTestHook C++ interface.  Our use
+// of VR runtimes are single-threaded at a time, and we initialize/uninitialize
 // as we switch between immersive and non-immersive sessions.
 // The mojo pointer is thread-affine, but we can keep the same mojo connection
 // by getting its PtrInfo so we temporarily make the test hook mojo pointer
-// live on the thread that we are using OpenVR on while OpenVR is initialized.
-class XRTestHookWrapper : public OpenVRTestHook {
+// live on the thread that we are using the VR runtime on while the runtime is
+// initialized.
+class XRTestHookWrapper : public VRTestHook {
  public:
   explicit XRTestHookWrapper(device_test::mojom::XRTestHookPtrInfo hook_info);
   virtual ~XRTestHookWrapper();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4a39942..8b6c59ba 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2681,7 +2681,7 @@
     "../browser/navigation_predictor/navigation_predictor_unittest.cc",
     "../browser/net/chrome_network_delegate_unittest.cc",
     "../browser/net/dns_probe_runner_unittest.cc",
-    "../browser/net/dns_probe_service_unittest.cc",
+    "../browser/net/dns_probe_service_factory_unittest.cc",
     "../browser/net/file_downloader_unittest.cc",
     "../browser/net/net_error_tab_helper_unittest.cc",
     "../browser/net/probe_message_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 d5ff23e..2be6f9e 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -298,6 +298,22 @@
           chrome.test.succeed();
         });
   },
+  // This test verifies that entering tablet mode works as expected.
+  function setTabletModeEnabled() {
+    chrome.autotestPrivate.setTabletModeEnabled(true, function(isEnabled){
+      chrome.test.assertTrue(isEnabled);
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
+  },
+  // This test verifies that leaving tablet mode works as expected.
+  function setTabletModeDisabled() {
+    chrome.autotestPrivate.setTabletModeEnabled(false, function(isEnabled){
+      chrome.test.assertFalse(isEnabled);
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
+  },
 ];
 
 var arcEnabledTests = [
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/manifest.json b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/manifest.json
new file mode 100644
index 0000000..651b0e8
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/manifest.json
@@ -0,0 +1,8 @@
+{
+  "name": "Service Worker-based background script",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "Test tabs APIs for service worker-based background scripts.",
+  "permissions": ["tabs"],
+  "background": {"service_worker_script": "service_worker_background.js"}
+}
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/service_worker_background.js b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/service_worker_background.js
new file mode 100644
index 0000000..e5de93cf5
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/tabs_basic/service_worker_background.js
@@ -0,0 +1,160 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var tabProps = [];
+
+var createTabUtil = function(urlToLoad, createdCallback) {
+  try {
+    chrome.tabs.create({url: urlToLoad}, function(tab) {
+      createdCallback({id: tab.id, url: tab.url});
+    });
+  } catch (e) {
+    chrome.test.fail(e);
+  }
+}
+
+var getTabUtil = function(tabId, getCallback) {
+  try {
+    chrome.tabs.get(tabId, function(tab) {
+      getCallback({id: tab.id, url: tab.url});
+    });
+  } catch (e) {
+    chrome.test.fail(e);
+  }
+}
+
+var queryTabUtil = function(queryProps, queryCallback) {
+  try {
+    chrome.tabs.query(queryProps, queryCallback);
+  } catch(e) {
+    chrome.test.fail(e);
+  }
+}
+
+chrome.test.runTests([
+  // Get the info for the tab that was automatically created.
+  function testTabQueryInitial() {
+    queryTabUtil({currentWindow: true}, function(tabs) {
+        chrome.test.assertEq(1, tabs.length);
+        tabProps.push({id: tabs[0].id, url: tabs[0].url});
+        chrome.test.succeed();
+    });
+  },
+  // Create a new tab.
+  function testTabCreate1() {
+    var expectedUrl = 'chrome://newtab/';
+    createTabUtil(expectedUrl, function(tabData) {
+      chrome.test.assertEq(expectedUrl, tabData.url);
+      tabProps.push(tabData);
+      chrome.test.succeed();
+    });
+  },
+  // Check that it exists.
+  function testTabGetAfterCreate1() {
+    var expectedId = tabProps[tabProps.length - 1].id;
+    var expectedUrl = tabProps[tabProps.length - 1].url;
+    getTabUtil(expectedId, function(tabData) {
+      chrome.test.assertEq(expectedId, tabData.id);
+      chrome.test.assertEq(expectedUrl, tabData.url);
+      chrome.test.succeed();
+    });
+  },
+  // Create another new tab.
+  function testTabCreate2() {
+    var expectedUrl = 'chrome://version/';
+    createTabUtil(expectedUrl, function(tabData) {
+      chrome.test.assertEq(expectedUrl, tabData.url);
+      tabProps.push(tabData);
+      chrome.test.succeed();
+    });
+  },
+  // Check that it also exists.
+  function testTabGetAfterCreate2() {
+    var expectedId = tabProps[tabProps.length - 1].id;
+    var expectedUrl = tabProps[tabProps.length - 1].url;
+    getTabUtil(expectedId, function(tabData) {
+      chrome.test.assertEq(expectedId, tabData.id);
+      chrome.test.assertEq(expectedUrl, tabData.url);
+      chrome.test.succeed();
+    });
+  },
+  // Verify that chrome.tabs.getCurrent doesn't work with background
+  // pages.
+  function testTabGetCurrent() {
+    try {
+      chrome.tabs.getCurrent(function(tab) {
+        chrome.test.assertEq('undefined', typeof(tab));
+        chrome.test.succeed();
+      });
+    } catch (e) {
+      chrome.test.fail(e);
+    }
+  },
+  // Duplicate the first tab created.
+  function testTabGetDuplicate() {
+    try {
+      chrome.tabs.duplicate(tabProps[0].id, function(tab) {
+        chrome.test.assertEq(tabProps[0].url, tab.url);
+        tabProps.push({id: tab.id, url: tab.url});
+        chrome.test.succeed();
+      })
+    } catch (e) {
+      chrome.test.fail(e);
+    }
+  },
+  // Check that the duplicate exists.
+  function testTabGet3() {
+    var expectedId = tabProps[tabProps.length - 1].id;
+    var expectedUrl = tabProps[tabProps.length - 1].url;
+    getTabUtil(expectedId, function(tabData) {
+      chrome.test.assertEq(expectedId, tabData.id);
+      chrome.test.assertEq(expectedUrl, tabData.url);
+      chrome.test.succeed();
+    });
+  },
+  // Query all the tabs and check their IDs and URLs are what
+  // we expect.
+  function testTabQuery2() {
+    queryTabUtil({currentWindow: true}, function(tabs) {
+      chrome.test.assertEq(tabProps.length, tabs.length);
+      var countFound = 0;
+      // This loop works because tab IDs are unique.
+      for (var i = 0; i < tabs.length; ++i) {
+        for (var j = 0; j < tabProps.length; ++j) {
+          if (tabs[i].id === tabProps[j].id &&
+              tabs[i].url === tabProps[j].url) {
+            ++countFound;
+            break;
+          }
+        }
+      }
+      chrome.test.assertEq(tabProps.length, countFound);
+      chrome.test.succeed();
+    });
+  },
+  // Remove all but the original tab. Removing them all will shut down the
+  // browser, which we don't want.
+  function testTabRemove() {
+    try {
+      var tabIds = [];
+      for (var i = 1; i < tabProps.length; ++i) {
+        tabIds.push(tabProps[i].id);
+      }
+      chrome.tabs.remove(tabIds, function() {
+        chrome.test.succeed();
+      });
+    } catch(e) {
+      chrome.test.fail(e);
+    }
+  },
+  // Check that there's only one remaining tab.
+  function testTabQuery3() {
+    queryTabUtil({currentWindow: true}, function(tabs) {
+      chrome.test.assertEq(1, tabs.length);
+      chrome.test.assertEq(tabs[0].id, tabProps[0].id);
+      chrome.test.assertEq(tabs[0].url, tabProps[0].url);
+      chrome.test.succeed();
+    });
+  },
+]);
diff --git a/chrome/test/data/webui/welcome/nux_ntp_background_test.js b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
index 6aca64a..fafcfe13 100644
--- a/chrome/test/data/webui/welcome/nux_ntp_background_test.js
+++ b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
@@ -8,15 +8,17 @@
     let backgrounds = [
       {
         id: 0,
-        title: 'Cat',
-        imageUrl: 'some/cute/photo/of/a/cat',
-        thumbnailClass: 'cat',
+        title: 'Art',
+        /* Image URLs are set to actual static images to prevent requesting
+         * an external image. */
+        imageUrl: '../images/ntp_thumbnails/art.jpg',
+        thumbnailClass: 'art',
       },
       {
         id: 1,
-        title: 'Venice',
-        imageUrl: 'some/scenic/photo/of/a/beach',
-        thumbnailClass: 'venice',
+        title: 'Cityscape',
+        imageUrl: '../images/ntp_thumbnails/cityscape.jpg',
+        thumbnailClass: 'cityscape',
       },
     ];
 
@@ -66,6 +68,29 @@
       }
     });
 
+    test('test previewing a background and going back to default', function() {
+      const options = testElement.shadowRoot.querySelectorAll(
+          '.ntp-background-grid-button');
+
+      options[1].click();
+      return testNtpBackgroundProxy.whenCalled('preloadImage').then(() => {
+        assertEquals(
+            testElement.$.backgroundPreview.style.backgroundImage,
+            `url("${backgrounds[0].imageUrl}")`);
+        assertTrue(
+            testElement.$.backgroundPreview.classList.contains('active'));
+
+        // go back to the default option, and pretend all CSS transitions
+        // have completed
+        options[0].click();
+        testElement.$.backgroundPreview.dispatchEvent(
+            new Event('transitionend'));
+        assertEquals(testElement.$.backgroundPreview.style.backgroundImage, '');
+        assertFalse(
+            testElement.$.backgroundPreview.classList.contains('active'));
+      });
+    });
+
     test('test disabling and enabling of the next button', function() {
       const nextButton = testElement.shadowRoot.querySelector('.action-button');
       assertTrue(nextButton.disabled);
diff --git a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
index 827593f..42bce1e2 100644
--- a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
+++ b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
@@ -7,6 +7,7 @@
   constructor() {
     super([
       'getBackgrounds',
+      'preloadImage',
       'setBackground',
     ]);
 
@@ -21,6 +22,12 @@
   }
 
   /** @override */
+  preloadImage(url) {
+    this.methodCalled('preloadImage');
+    return Promise.resolve();
+  }
+
+  /** @override */
   setBackground(id) {
     this.methodCalled('setBackground', id);
   }
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_inline_identity_available.html b/chrome/test/data/xr/e2e_test_files/html/test_inline_identity_available.html
index 7970739..c2ef483 100644
--- a/chrome/test/data/xr/e2e_test_files/html/test_inline_identity_available.html
+++ b/chrome/test/data/xr/e2e_test_files/html/test_inline_identity_available.html
@@ -32,10 +32,8 @@
       let step = "supportsInline";
       navigator.xr.supportsSessionMode('inline')
       .then(()=> {
-        let outputCanvas = document.createElement('canvas');
-        let ctx = outputCanvas.getContext('xrpresent');
         step = "requestSession";
-        return navigator.xr.requestSession({ outputContext: ctx });
+        return navigator.xr.requestSession();
       })
       .then((xrSession) => {
         session = xrSession;
@@ -54,7 +52,11 @@
           throw 'Failed to get WebGL context';
         }
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
         session.requestAnimationFrame(onRequestAnimationFrame);
       })
       .catch((err) => {
diff --git a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
index 6fccd584..f12fd36 100644
--- a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
+++ b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
@@ -97,11 +97,13 @@
         console.info('Immersive AR session request rejected with: ' + error);
         console.info('Attempting to fall back to legacy AR mode');
         let sessionOptions = {
-          mode: 'legacy-inline-ar',
-          outputContext: webglCanvas.getContext('xrpresent'),
+          mode: 'legacy-inline-ar'
         }
         navigator.xr.requestSession(sessionOptions).then(
             (session) => {
+          session.updateRenderState({
+              outputContext: webglCanvas.getContext('xrpresent')
+          });
           console.info('Legacy AR session request succeeded');
           sessionInfos[sessionTypes.AR].currentSession = session;
           onSessionStarted(session);
@@ -199,8 +201,11 @@
   // inline session creation.
   if (typeof shouldAutoCreateNonImmersiveSession === 'undefined'
       || shouldAutoCreateNonImmersiveSession === true) {
-    navigator.xr.requestSession({outputContext: ctx})
+    navigator.xr.requestSession()
         .then((session) => {
+          session.updateRenderState({
+            outputContext: ctx
+          });
           onSessionStarted(session);
         }).then( () => {
           initializationSteps['magicWindowStarted'] = true;
diff --git a/chromecast/base/BUILD.gn b/chromecast/base/BUILD.gn
index 75645e4..306ad19 100644
--- a/chromecast/base/BUILD.gn
+++ b/chromecast/base/BUILD.gn
@@ -228,7 +228,7 @@
       "//chromecast:chromecast_buildflags",
       "//chromecast/browser:jni_headers",
     ]
-  } else if (chromecast_branding == "public") {
+  } else if (chromecast_branding == "public" || is_fuchsia) {
     sources += [ "cast_sys_info_util_simple.cc" ]
   }
 }
@@ -249,7 +249,7 @@
 
 # Note: Android links //chromecast/base:cast_sys_info statically.
 cast_source_set("cast_sys_info_shlib") {
-  if (!is_android) {
+  if (!is_android && !is_fuchsia) {
     sources = [
       "cast_sys_info_util_shlib.cc",
     ]
diff --git a/chromecast/base/cast_sys_info_android.cc b/chromecast/base/cast_sys_info_android.cc
index 5302135b..eb81b46 100644
--- a/chromecast/base/cast_sys_info_android.cc
+++ b/chromecast/base/cast_sys_info_android.cc
@@ -130,19 +130,4 @@
   return "";
 }
 
-std::string CastSysInfoAndroid::GetGlVendor() {
-  NOTREACHED() << "GL information shouldn't be requested on Android.";
-  return "";
-}
-
-std::string CastSysInfoAndroid::GetGlRenderer() {
-  NOTREACHED() << "GL information shouldn't be requested on Android.";
-  return "";
-}
-
-std::string CastSysInfoAndroid::GetGlVersion() {
-  NOTREACHED() << "GL information shouldn't be requested on Android.";
-  return "";
-}
-
 }  // namespace chromecast
diff --git a/chromecast/base/cast_sys_info_android.h b/chromecast/base/cast_sys_info_android.h
index 4f1fe94..6299055 100644
--- a/chromecast/base/cast_sys_info_android.h
+++ b/chromecast/base/cast_sys_info_android.h
@@ -37,9 +37,6 @@
   std::string GetFactoryLocale(std::string* second_locale) override;
   std::string GetWifiInterface() override;
   std::string GetApInterface() override;
-  std::string GetGlVendor() override;
-  std::string GetGlRenderer() override;
-  std::string GetGlVersion() override;
 
  private:
   const base::android::BuildInfo* const build_info_;
diff --git a/chromecast/base/cast_sys_info_dummy.cc b/chromecast/base/cast_sys_info_dummy.cc
index 4c210bb..1170884 100644
--- a/chromecast/base/cast_sys_info_dummy.cc
+++ b/chromecast/base/cast_sys_info_dummy.cc
@@ -73,18 +73,6 @@
   return ap_interface_;
 }
 
-std::string CastSysInfoDummy::GetGlVendor() {
-  return gl_vendor_;
-}
-
-std::string CastSysInfoDummy::GetGlRenderer() {
-  return gl_renderer_;
-}
-
-std::string CastSysInfoDummy::GetGlVersion() {
-  return gl_version_;
-}
-
 void CastSysInfoDummy::SetBuildTypeForTesting(
     CastSysInfo::BuildType build_type) {
   build_type_ = build_type;
@@ -149,16 +137,4 @@
   ap_interface_ = ap_interface;
 }
 
-void CastSysInfoDummy::SetGlVendorForTesting(const std::string& gl_vendor) {
-  gl_vendor_ = gl_vendor;
-}
-
-void CastSysInfoDummy::SetGlRendererForTesting(const std::string& gl_renderer) {
-  gl_renderer_ = gl_renderer;
-}
-
-void CastSysInfoDummy::SetGlVersionForTesting(const std::string& gl_version) {
-  gl_version_ = gl_version;
-}
-
 }  // namespace chromecast
diff --git a/chromecast/base/cast_sys_info_dummy.h b/chromecast/base/cast_sys_info_dummy.h
index 10d03a8..9cfe7cf 100644
--- a/chromecast/base/cast_sys_info_dummy.h
+++ b/chromecast/base/cast_sys_info_dummy.h
@@ -32,9 +32,6 @@
   std::string GetFactoryLocale(std::string* second_locale) override;
   std::string GetWifiInterface() override;
   std::string GetApInterface() override;
-  std::string GetGlVendor() override;
-  std::string GetGlRenderer() override;
-  std::string GetGlVersion() override;
 
   void SetBuildTypeForTesting(BuildType build_type);
   void SetSystemReleaseChannelForTesting(
@@ -50,9 +47,6 @@
   void SetFactoryLocaleForTesting(const std::string& factory_locale);
   void SetWifiInterfaceForTesting(const std::string& wifi_interface);
   void SetApInterfaceForTesting(const std::string& ap_interface);
-  void SetGlVendorForTesting(const std::string& gl_vendor);
-  void SetGlRendererForTesting(const std::string& gl_renderer);
-  void SetGlVersionForTesting(const std::string& gl_version);
 
  private:
   BuildType build_type_;
@@ -68,9 +62,6 @@
   std::string factory_locale_;
   std::string wifi_interface_;
   std::string ap_interface_;
-  std::string gl_vendor_;
-  std::string gl_renderer_;
-  std::string gl_version_;
 
   DISALLOW_COPY_AND_ASSIGN(CastSysInfoDummy);
 };
diff --git a/chromecast/public/cast_sys_info.h b/chromecast/public/cast_sys_info.h
index 569d179..8fc723b 100644
--- a/chromecast/public/cast_sys_info.h
+++ b/chromecast/public/cast_sys_info.h
@@ -64,12 +64,6 @@
   virtual std::string GetWifiInterface() = 0;
   // Returns the name of the software AP interface.
   virtual std::string GetApInterface() = 0;
-
-  // The following three APIs are deprecated and never called.
-  // TODO(halliwell): Remove them in the next system update.
-  virtual std::string GetGlVendor() = 0;
-  virtual std::string GetGlRenderer() = 0;
-  virtual std::string GetGlVersion() = 0;
 };
 
 }  // namespace chromecast
diff --git a/chromeos/components/drivefs/drivefs_host.cc b/chromeos/components/drivefs/drivefs_host.cc
index 158b70c..5c9eec11 100644
--- a/chromeos/components/drivefs/drivefs_host.cc
+++ b/chromeos/components/drivefs/drivefs_host.cc
@@ -19,7 +19,6 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/platform/platform_channel_endpoint.h"
 #include "mojo/public/cpp/system/invitation.h"
-#include "services/identity/public/mojom/constants.mojom.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/service_manager/public/cpp/connector.h"
diff --git a/chromeos/dbus/fake_power_manager_client.cc b/chromeos/dbus/fake_power_manager_client.cc
index 8220148..8720f6b 100644
--- a/chromeos/dbus/fake_power_manager_client.cc
+++ b/chromeos/dbus/fake_power_manager_client.cc
@@ -263,12 +263,9 @@
     return;
   }
 
-  // Check if client tag already exists. Return error iff it does.
-  if (base::ContainsKey(client_timer_ids_, tag)) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), std::vector<TimerId>()));
-    return;
-  }
+  // Just like the real implementation, delete any old timers associated with
+  // |tag|.
+  DeleteArcTimersInternal(tag);
 
   // First, ensure that there are no duplicate clocks in the arguments. Return
   // error if there are.
@@ -323,19 +320,7 @@
 
 void FakePowerManagerClient::DeleteArcTimers(const std::string& tag,
                                              VoidDBusMethodCallback callback) {
-  // Retrieve all timer ids associated with |tag|. Delete all timers associated
-  // with these timer ids. Return true even if |tag| isn't found.
-  auto it = client_timer_ids_.find(tag);
-  if (it == client_timer_ids_.end()) {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), true));
-    return;
-  }
-
-  for (auto timer_id : it->second)
-    timer_expiration_fds_.erase(timer_id);
-
-  client_timer_ids_.erase(it);
+  DeleteArcTimersInternal(tag);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(std::move(callback), true));
 }
@@ -440,6 +425,19 @@
   --num_pending_suspend_readiness_callbacks_;
 }
 
+void FakePowerManagerClient::DeleteArcTimersInternal(const std::string& tag) {
+  // Retrieve all timer ids associated with |tag|. Delete all timers associated
+  // with these timer ids.
+  auto it = client_timer_ids_.find(tag);
+  if (it == client_timer_ids_.end())
+    return;
+
+  for (auto timer_id : it->second)
+    timer_expiration_fds_.erase(timer_id);
+
+  client_timer_ids_.erase(it);
+}
+
 void FakePowerManagerClient::SetPowerPolicyQuitClosure(
     base::OnceClosure quit_closure) {
   power_policy_quit_closure_ = std::move(quit_closure);
diff --git a/chromeos/dbus/fake_power_manager_client.h b/chromeos/dbus/fake_power_manager_client.h
index 6c1b3bf..45a1573 100644
--- a/chromeos/dbus/fake_power_manager_client.h
+++ b/chromeos/dbus/fake_power_manager_client.h
@@ -191,6 +191,9 @@
   // Notifies |observers_| that |props_| has been updated.
   void NotifyObservers();
 
+  // Deletes all timers, if any, associated with |tag|.
+  void DeleteArcTimersInternal(const std::string& tag);
+
   base::ObserverList<Observer>::Unchecked observers_;
 
   // Last policy passed to SetPolicy().
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index 4a2e1d2..d25519f 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -35,6 +35,9 @@
 
 namespace {
 
+// Ignore changes to signal strength less than this value for active networks.
+const int kSignalStrengthChangeThreshold = 5;
+
 bool ConnectionStateChanged(const NetworkState* network,
                             const std::string& prev_connection_state,
                             bool prev_is_captive_portal) {
@@ -99,13 +102,16 @@
       : guid_(network->guid()),
         connection_state_(network->connection_state()),
         activation_state_(network->activation_state()),
-        connect_requested_(network->connect_requested()) {}
+        connect_requested_(network->connect_requested()),
+        signal_strength_(network->signal_strength()) {}
 
   bool MatchesNetworkState(const NetworkState* network) {
     return guid_ == network->guid() &&
            connection_state_ == network->connection_state() &&
            activation_state_ == network->activation_state() &&
-           connect_requested_ == network->connect_requested();
+           connect_requested_ == network->connect_requested() &&
+           (abs(signal_strength_ - network->signal_strength()) <
+            kSignalStrengthChangeThreshold);
   }
 
  private:
@@ -119,6 +125,8 @@
   const std::string activation_state_;
   // The connect_requested state affects 'connecting' in the UI.
   const bool connect_requested_;
+  // We care about signal strength changes to active networks.
+  const int signal_strength_;
 };
 
 const char NetworkStateHandler::kDefaultCheckPortalList[] =
@@ -1359,6 +1367,7 @@
   if (request_update)
     RequestUpdateForNetwork(service_path);
 
+  bool notify_active = false;
   std::string value_str;
   value.GetAsString(&value_str);
   if (key == shill::kSignalStrengthProperty || key == shill::kWifiBSsid ||
@@ -1372,6 +1381,9 @@
       return;
     // Otherwise do not trigger 'default network changed'.
     notify_default = false;
+    // Notify signal strength changes for active networks.
+    if (key == shill::kSignalStrengthProperty)
+      notify_active = true;
   }
 
   LogPropertyUpdated(network, key, value);
@@ -1379,6 +1391,8 @@
     NotifyNetworkConnectionStateChanged(network);
   if (notify_default)
     NotifyDefaultNetworkChanged();
+  if (notify_active)
+    NotifyIfActiveNetworksChanged();
   NotifyNetworkPropertiesUpdated(network);
   if (sort_networks)
     SortNetworkList(true /* ensure_cellular */);
diff --git a/chromeos/network/network_state_handler_observer.h b/chromeos/network/network_state_handler_observer.h
index 6e1d125..dc25e846 100644
--- a/chromeos/network/network_state_handler_observer.h
+++ b/chromeos/network/network_state_handler_observer.h
@@ -42,8 +42,9 @@
   virtual void NetworkConnectionStateChanged(const NetworkState* network);
 
   // Triggered when the connection state of any current or previously active
-  // (connected or connecting) network changes. Provides the current list of
-  // active networks, which may include a VPN.
+  // (connected or connecting) network changes. Includes significant changes to
+  // the signal strength. Provides the current list of active networks, which
+  // may include a VPN.
   virtual void ActiveNetworksChanged(
       const std::vector<const NetworkState*>& active_networks);
 
diff --git a/chromeos/network/network_state_handler_unittest.cc b/chromeos/network/network_state_handler_unittest.cc
index 263ee59..42b4c6f 100644
--- a/chromeos/network/network_state_handler_unittest.cc
+++ b/chromeos/network/network_state_handler_unittest.cc
@@ -952,8 +952,9 @@
   EXPECT_EQ(11, wifi->signal_strength());
   // The change should trigger an additional properties updated event.
   EXPECT_EQ(2, test_observer_->PropertyUpdatesForService(wifi1));
+  EXPECT_EQ(1u, test_observer_->active_network_change_count());
+  // Signal strength changes do not trigger a default network change.
   EXPECT_EQ(0u, test_observer_->default_network_change_count());
-  EXPECT_EQ(0u, test_observer_->active_network_change_count());
 }
 
 TEST_F(NetworkStateHandlerTest, ServicePropertyChangedNotIneterstingInactive) {
@@ -1629,6 +1630,22 @@
   EXPECT_EQ(expected_active_network_paths,
             test_observer_->active_network_paths());
 
+  // Modify the wifi signal strength, an observer update should occur.
+  test_observer_->reset_change_counts();
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kSignalStrengthProperty,
+                                    base::Value(100));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, test_observer_->active_network_change_count());
+
+  // A small change should not trigger an update.
+  test_observer_->reset_change_counts();
+  service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
+                                    shill::kSignalStrengthProperty,
+                                    base::Value(99));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, test_observer_->active_network_change_count());
+
   // Disconnect Wifi1.
   test_observer_->reset_change_counts();
   service_test_->SetServiceProperty(kShillManagerClientStubDefaultWifi,
diff --git a/chromeos/network/network_state_test_helper.cc b/chromeos/network/network_state_test_helper.cc
index 078f577..8246d8d 100644
--- a/chromeos/network/network_state_test_helper.cc
+++ b/chromeos/network/network_state_test_helper.cc
@@ -147,6 +147,7 @@
     int signal_strength) {
   auto network = std::make_unique<NetworkState>(id);
   network->SetGuid(id);
+  network->set_name(id);
   network->set_type(type);
   network->set_visible(true);
   network->SetConnectionState(connection_state);
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc
index 462cea8..8240643 100644
--- a/chromeos/network/onc/onc_translation_tables.cc
+++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -474,7 +474,9 @@
     *shill_value = table[i].shill_value;
     return true;
   }
-  LOG(ERROR) << "Value '" << onc_value << "' cannot be translated to Shill";
+  LOG(ERROR) << "Value '" << onc_value << "' cannot be translated to Shill"
+             << " table[0]: " << table[0].onc_value << " => "
+             << table[0].shill_value;
   return false;
 }
 
@@ -487,7 +489,9 @@
     *onc_value = table[i].onc_value;
     return true;
   }
-  LOG(ERROR) << "Value '" << shill_value << "' cannot be translated to ONC";
+  LOG(ERROR) << "Value '" << shill_value << "' cannot be translated to ONC"
+             << " table[0]: " << table[0].shill_value << " => "
+             << table[0].onc_value;
   return false;
 }
 
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index 742f4c3..d5b58c6 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -33,6 +33,7 @@
   deps = [
     "//base",
     "//build/util:webkit_version",
+    "//chromeos:chromeos_constants",
     "//chromeos/assistant:buildflags",
     "//chromeos/audio",
     "//chromeos/dbus",
diff --git a/chromeos/services/assistant/assistant_manager_service.h b/chromeos/services/assistant/assistant_manager_service.h
index d90fde2..ca58a792 100644
--- a/chromeos/services/assistant/assistant_manager_service.h
+++ b/chromeos/services/assistant/assistant_manager_service.h
@@ -31,10 +31,11 @@
 
   ~AssistantManagerService() override = default;
 
-  // Start the assistant in the background with |access_token|. When the service
-  // is fully started |callback| will be called on the thread where ctor was
-  // run.
-  virtual void Start(const std::string& access_token,
+  // Start the assistant in the background with |access_token|, where the
+  // token can be nullopt when the service is being started under the signed
+  // out mode. When the service is fully started |callback| will be called on
+  // the thread where ctor was run.
+  virtual void Start(const base::Optional<std::string>& access_token,
                      bool enable_hotword,
                      base::OnceClosure callback) = 0;
 
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 39e7049..137c8c5 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -130,9 +130,10 @@
   background_thread_.Stop();
 }
 
-void AssistantManagerServiceImpl::Start(const std::string& access_token,
-                                        bool enable_hotword,
-                                        base::OnceClosure post_init_callback) {
+void AssistantManagerServiceImpl::Start(
+    const base::Optional<std::string>& access_token,
+    bool enable_hotword,
+    base::OnceClosure post_init_callback) {
   DCHECK(!assistant_manager_);
   DCHECK_EQ(state_, State::STOPPED);
 
@@ -179,6 +180,8 @@
   if (!assistant_manager_)
     return;
 
+  DCHECK(!access_token.empty());
+
   VLOG(1) << "Set access token.";
   // Push the |access_token| we got as an argument into AssistantManager before
   // starting to ensure that all server requests will be authenticated once
@@ -747,7 +750,7 @@
 }
 
 void AssistantManagerServiceImpl::StartAssistantInternal(
-    const std::string& access_token) {
+    const base::Optional<std::string>& access_token) {
   DCHECK(background_thread_.task_runner()->BelongsToCurrentThread());
 
   base::AutoLock lock(new_assistant_manager_lock_);
@@ -772,8 +775,10 @@
   if (server_experiment_ids.size() > 0)
     assistant_manager_internal->AddExtraExperimentIds(server_experiment_ids);
 
-  new_assistant_manager_->SetAuthTokens(
-      {std::pair<std::string, std::string>(kUserID, access_token)});
+  if (!service_->is_signed_out_mode()) {
+    new_assistant_manager_->SetAuthTokens(
+        {std::pair<std::string, std::string>(kUserID, access_token.value())});
+  }
   new_assistant_manager_->Start();
 }
 
@@ -895,6 +900,11 @@
   internal_options->SetClientControlEnabled(
       assistant::features::IsRoutinesEnabled());
 
+  if (service_->is_signed_out_mode()) {
+    internal_options->SetUserCredentialMode(
+        assistant_client::InternalOptions::UserCredentialMode::SIGNED_OUT);
+  }
+
   if (base::FeatureList::IsEnabled(assistant::features::kAssistantVoiceMatch) &&
       assistant_settings_manager_->speaker_id_enrollment_done()) {
     internal_options->EnableRequireVoiceMatchVerification();
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 8a09daf..b4f4a36d 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -97,7 +97,7 @@
   ~AssistantManagerServiceImpl() override;
 
   // assistant::AssistantManagerService overrides
-  void Start(const std::string& access_token,
+  void Start(const base::Optional<std::string>& access_token,
              bool enable_hotword,
              base::OnceClosure callback) override;
   void Stop() override;
@@ -181,7 +181,7 @@
   }
 
  private:
-  void StartAssistantInternal(const std::string& access_token);
+  void StartAssistantInternal(const base::Optional<std::string>& access_token);
   void PostInitAssistant(base::OnceClosure post_init_callback);
 
   // Update device id, type and locale
diff --git a/chromeos/services/assistant/fake_assistant_manager_service_impl.cc b/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
index 6f3fc5c8..780bad0 100644
--- a/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/fake_assistant_manager_service_impl.cc
@@ -13,9 +13,10 @@
 
 FakeAssistantManagerServiceImpl::~FakeAssistantManagerServiceImpl() = default;
 
-void FakeAssistantManagerServiceImpl::Start(const std::string& access_token,
-                                            bool enable_hotword,
-                                            base::OnceClosure callback) {
+void FakeAssistantManagerServiceImpl::Start(
+    const base::Optional<std::string>& access_token,
+    bool enable_hotword,
+    base::OnceClosure callback) {
   state_ = State::RUNNING;
 
   if (callback)
diff --git a/chromeos/services/assistant/fake_assistant_manager_service_impl.h b/chromeos/services/assistant/fake_assistant_manager_service_impl.h
index 33eba93..a452eef 100644
--- a/chromeos/services/assistant/fake_assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/fake_assistant_manager_service_impl.h
@@ -27,7 +27,7 @@
   ~FakeAssistantManagerServiceImpl() override;
 
   // assistant::AssistantManagerService overrides
-  void Start(const std::string& access_token,
+  void Start(const base::Optional<std::string>& access_token,
              bool enable_hotword,
              base::OnceClosure callback) override;
   void Stop() override;
diff --git a/chromeos/services/assistant/service.cc b/chromeos/services/assistant/service.cc
index b7ba8b0b..3d3f4a0a 100644
--- a/chromeos/services/assistant/service.cc
+++ b/chromeos/services/assistant/service.cc
@@ -9,6 +9,7 @@
 
 #include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/logging.h"
 #include "base/rand_util.h"
 #include "base/single_thread_task_runner.h"
@@ -16,6 +17,7 @@
 #include "build/buildflag.h"
 #include "chromeos/assistant/buildflags.h"
 #include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
 #include "chromeos/services/assistant/assistant_manager_service.h"
@@ -83,6 +85,10 @@
 Service::~Service() = default;
 
 void Service::RequestAccessToken() {
+  // Bypass access token fetching under signed out mode.
+  if (is_signed_out_mode_)
+    return;
+
   VLOG(1) << "Start requesting access token.";
   GetIdentityAccessor()->GetPrimaryAccountInfo(base::BindOnce(
       &Service::GetPrimaryAccountInfoCallback, base::Unretained(this)));
@@ -188,7 +194,8 @@
   if (!assistant_state_.hotword_enabled().has_value() ||
       !assistant_state_.settings_enabled().has_value() ||
       !assistant_state_.hotword_always_on().has_value() ||
-      !assistant_state_.locale().has_value() || !access_token_.has_value()) {
+      !assistant_state_.locale().has_value() ||
+      (!access_token_.has_value() && !is_signed_out_mode_)) {
     // Assistant state has not finished initialization, let's wait.
     return;
   }
@@ -200,7 +207,8 @@
     case AssistantManagerService::State::STOPPED:
       if (assistant_state_.settings_enabled().value()) {
         assistant_manager_service_->Start(
-            access_token_.value(), ShouldEnableHotword(),
+            is_signed_out_mode_ ? base::nullopt : access_token_,
+            ShouldEnableHotword(),
             base::BindOnce(
                 [](scoped_refptr<base::SequencedTaskRunner> task_runner,
                    base::OnceCallback<void()> callback) {
@@ -215,7 +223,8 @@
     case AssistantManagerService::State::RUNNING:
     case AssistantManagerService::State::STARTED:
       if (assistant_state_.settings_enabled().value()) {
-        assistant_manager_service_->SetAccessToken(access_token_.value());
+        if (!is_signed_out_mode_)
+          assistant_manager_service_->SetAccessToken(access_token_.value());
         assistant_manager_service_->EnableHotword(ShouldEnableHotword());
       } else {
         StopAssistantManagerService();
@@ -237,6 +246,14 @@
   device_actions_ = std::move(device_actions);
   assistant_state_.Init(service_binding_.GetConnector());
   assistant_state_.AddObserver(this);
+
+  // Don't fetch token for test.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          chromeos::switches::kDisableGaiaServices)) {
+    is_signed_out_mode_ = true;
+    return;
+  }
+
   RequestAccessToken();
 }
 
diff --git a/chromeos/services/assistant/service.h b/chromeos/services/assistant/service.h
index c69af88e..92ee647 100644
--- a/chromeos/services/assistant/service.h
+++ b/chromeos/services/assistant/service.h
@@ -95,6 +95,8 @@
     return main_task_runner_;
   }
 
+  bool is_signed_out_mode() const { return is_signed_out_mode_; }
+
   void RequestAccessToken();
 
   void SetIdentityAccessorForTesting(
@@ -187,6 +189,9 @@
   bool locked_ = false;
   // Whether the power source is connected.
   bool power_source_connected_ = false;
+  // In the signed-out mode, we are going to run Assistant service without
+  // using user's signed in account information.
+  bool is_signed_out_mode_ = false;
 
   base::Optional<std::string> access_token_;
 
diff --git a/chromeos/services/device_sync/cryptauth_client_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_client_impl_unittest.cc
index 4c7fd0b..86a083a 100644
--- a/chromeos/services/device_sync/cryptauth_client_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_client_impl_unittest.cc
@@ -98,9 +98,9 @@
 
 }  // namespace
 
-class CryptAuthClientTest : public testing::Test {
+class DeviceSyncCryptAuthClientTest : public testing::Test {
  protected:
-  CryptAuthClientTest()
+  DeviceSyncCryptAuthClientTest()
       : api_call_flow_(new StrictMock<MockCryptAuthApiCallFlow>()),
         serialized_request_(std::string()) {
     shared_factory_ =
@@ -171,7 +171,7 @@
   CryptAuthApiCallFlow::ErrorCallback flow_error_callback_;
 };
 
-TEST_F(CryptAuthClientTest, GetMyDevicesSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, GetMyDevicesSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "getmydevices?alt=proto");
@@ -216,7 +216,7 @@
   EXPECT_TRUE(result_proto.devices(1).unlockable());
 }
 
-TEST_F(CryptAuthClientTest, GetMyDevicesFailure) {
+TEST_F(DeviceSyncCryptAuthClientTest, GetMyDevicesFailure) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "getmydevices?alt=proto");
@@ -235,7 +235,7 @@
   EXPECT_EQ(NetworkRequestError::kInternalServerError, error);
 }
 
-TEST_F(CryptAuthClientTest, FindEligibleUnlockDevicesSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, FindEligibleUnlockDevicesSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "findeligibleunlockdevices?alt=proto");
@@ -283,7 +283,7 @@
             result_proto.ineligible_devices(0).reasons(0));
 }
 
-TEST_F(CryptAuthClientTest, FindEligibleUnlockDevicesFailure) {
+TEST_F(DeviceSyncCryptAuthClientTest, FindEligibleUnlockDevicesFailure) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "findeligibleunlockdevices?alt=proto");
@@ -304,7 +304,7 @@
   EXPECT_EQ(NetworkRequestError::kAuthenticationError, error);
 }
 
-TEST_F(CryptAuthClientTest, FindEligibleForPromotionSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, FindEligibleForPromotionSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "findeligibleforpromotion?alt=proto");
@@ -327,7 +327,7 @@
   FinishApiCallFlow(&response_proto);
 }
 
-TEST_F(CryptAuthClientTest, SendDeviceSyncTickleSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, SendDeviceSyncTickleSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "senddevicesynctickle?alt=proto");
@@ -350,7 +350,7 @@
   FinishApiCallFlow(&response_proto);
 }
 
-TEST_F(CryptAuthClientTest, ToggleEasyUnlockSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, ToggleEasyUnlockSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "toggleeasyunlock?alt=proto");
@@ -379,7 +379,7 @@
   FinishApiCallFlow(&response_proto);
 }
 
-TEST_F(CryptAuthClientTest, SetupEnrollmentSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, SetupEnrollmentSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/enrollment/"
       "setup?alt=proto");
@@ -429,7 +429,7 @@
   EXPECT_EQ("ephemeral_key", result_proto.infos(0).server_ephemeral_key());
 }
 
-TEST_F(CryptAuthClientTest, FinishEnrollmentSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, FinishEnrollmentSuccess) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/enrollment/"
       "finish?alt=proto");
@@ -465,7 +465,7 @@
   EXPECT_EQ("OK", result_proto.status());
 }
 
-TEST_F(CryptAuthClientTest, SyncKeysSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, SyncKeysSuccess) {
   ExpectRequest(
       "https://cryptauthenrollment.testgoogleapis.com/v1:syncKeys?alt=proto");
 
@@ -497,7 +497,7 @@
   EXPECT_EQ(kRandomSessionId, result_proto.random_session_id());
 }
 
-TEST_F(CryptAuthClientTest, EnrollKeysSuccess) {
+TEST_F(DeviceSyncCryptAuthClientTest, EnrollKeysSuccess) {
   ExpectRequest(
       "https://cryptauthenrollment.testgoogleapis.com/v1:enrollKeys?alt=proto");
 
@@ -535,7 +535,7 @@
       result_proto.enroll_single_key_responses(0).certificate(0).common_name());
 }
 
-TEST_F(CryptAuthClientTest, FetchAccessTokenFailure) {
+TEST_F(DeviceSyncCryptAuthClientTest, FetchAccessTokenFailure) {
   NetworkRequestError error;
   client_->GetMyDevices(
       cryptauth::GetMyDevicesRequest(),
@@ -549,7 +549,7 @@
   EXPECT_EQ(NetworkRequestError::kAuthenticationError, error);
 }
 
-TEST_F(CryptAuthClientTest, ParseResponseProtoFailure) {
+TEST_F(DeviceSyncCryptAuthClientTest, ParseResponseProtoFailure) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "getmydevices?alt=proto");
@@ -568,7 +568,8 @@
   EXPECT_EQ(NetworkRequestError::kResponseMalformed, error);
 }
 
-TEST_F(CryptAuthClientTest, MakeSecondRequestBeforeFirstRequestSucceeds) {
+TEST_F(DeviceSyncCryptAuthClientTest,
+       MakeSecondRequestBeforeFirstRequestSucceeds) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "getmydevices?alt=proto");
@@ -607,7 +608,8 @@
   EXPECT_EQ(kPublicKey1, result_proto.devices(0).public_key());
 }
 
-TEST_F(CryptAuthClientTest, MakeSecondRequestAfterFirstRequestSucceeds) {
+TEST_F(DeviceSyncCryptAuthClientTest,
+       MakeSecondRequestAfterFirstRequestSucceeds) {
   // Make first request successfully.
   {
     ExpectRequest(
@@ -643,7 +645,7 @@
   }
 }
 
-TEST_F(CryptAuthClientTest, DeviceClassifierIsSet) {
+TEST_F(DeviceSyncCryptAuthClientTest, DeviceClassifierIsSet) {
   ExpectRequest(
       "https://www.testgoogleapis.com/cryptauth/v1/deviceSync/"
       "getmydevices?alt=proto");
@@ -674,7 +676,7 @@
             DeviceTypeStringToEnum(device_classifier.device_type()));
 }
 
-TEST_F(CryptAuthClientTest, GetAccessTokenUsed) {
+TEST_F(DeviceSyncCryptAuthClientTest, GetAccessTokenUsed) {
   EXPECT_TRUE(client_->GetAccessTokenUsed().empty());
 
   ExpectRequest(
diff --git a/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc b/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
index e64817f46..4d27af8 100644
--- a/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_bundle_unittest.cc
@@ -19,14 +19,14 @@
 
 }  // namespace
 
-TEST(CryptAuthKeyBundleTest, CreateKeyBundle) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, CreateKeyBundle) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   EXPECT_EQ(bundle.name(), CryptAuthKeyBundle::Name::kUserKeyPair);
   EXPECT_TRUE(bundle.handle_to_key_map().empty());
   EXPECT_FALSE(bundle.key_directive());
 }
 
-TEST(CryptAuthKeyBundleTest, SetKeyDirective) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, SetKeyDirective) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   cryptauthv2::KeyDirective key_directive;
   bundle.set_key_directive(key_directive);
@@ -35,7 +35,7 @@
             key_directive.SerializeAsString());
 }
 
-TEST(CryptAuthKeyBundleTest, AddKey) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                    cryptauthv2::KeyType::RAW256, kFakeSymmetricKeyHandle);
@@ -48,7 +48,7 @@
   EXPECT_EQ(it->second, key);
 }
 
-TEST(CryptAuthKeyBundleTest, AddKey_Inactive) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_Inactive) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256,
@@ -71,7 +71,7 @@
             CryptAuthKey::Status::kInactive);
 }
 
-TEST(CryptAuthKeyBundleTest, AddKey_ActiveKeyDeactivatesOthers) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_ActiveKeyDeactivatesOthers) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256,
@@ -94,7 +94,7 @@
             CryptAuthKey::Status::kActive);
 }
 
-TEST(CryptAuthKeyBundleTest, AddKey_ReplaceKeyWithSameHandle) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, AddKey_ReplaceKeyWithSameHandle) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256, "same-handle");
@@ -110,7 +110,7 @@
             asymmetric_key);
 }
 
-TEST(CryptAuthKeyBundleTest, GetActiveKey_DoesNotExist) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, GetActiveKey_DoesNotExist) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   EXPECT_FALSE(bundle.GetActiveKey());
 
@@ -121,7 +121,7 @@
   EXPECT_FALSE(bundle.GetActiveKey());
 }
 
-TEST(CryptAuthKeyBundleTest, GetActiveKey_Exists) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, GetActiveKey_Exists) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
@@ -136,7 +136,7 @@
   EXPECT_EQ(*bundle.GetActiveKey(), asymmetric_key);
 }
 
-TEST(CryptAuthKeyBundleTest, SetActiveKey_InactiveToActive) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, SetActiveKey_InactiveToActive) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
@@ -158,7 +158,7 @@
             CryptAuthKey::Status::kInactive);
 }
 
-TEST(CryptAuthKeyBundleTest, SetActiveKey_ActiveToActive) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, SetActiveKey_ActiveToActive) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
@@ -180,7 +180,7 @@
             CryptAuthKey::Status::kActive);
 }
 
-TEST(CryptAuthKeyBundleTest, DeactivateKeys) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, DeactivateKeys) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
@@ -202,7 +202,7 @@
             CryptAuthKey::Status::kInactive);
 }
 
-TEST(CryptAuthKeyBundleTest, DeleteKey) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, DeleteKey) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
@@ -214,7 +214,7 @@
   EXPECT_TRUE(bundle.handle_to_key_map().empty());
 }
 
-TEST(CryptAuthKeyBundleTest, ToAndFromDictionary_Trivial) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, ToAndFromDictionary_Trivial) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   base::Optional<CryptAuthKeyBundle> bundle_from_dict =
       CryptAuthKeyBundle::FromDictionary(bundle.AsDictionary());
@@ -222,7 +222,7 @@
   EXPECT_EQ(*bundle_from_dict, bundle);
 }
 
-TEST(CryptAuthKeyBundleTest, ToAndFromDictionary) {
+TEST(DeviceSyncCryptAuthKeyBundleTest, ToAndFromDictionary) {
   CryptAuthKeyBundle bundle(CryptAuthKeyBundle::Name::kUserKeyPair);
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kInactive,
                              cryptauthv2::KeyType::RAW256,
diff --git a/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
index 39d086e..66fd965 100644
--- a/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_creator_impl_unittest.cc
@@ -72,10 +72,10 @@
 
 }  // namespace
 
-class CryptAuthKeyCreatorImplTest : public testing::Test {
+class DeviceSyncCryptAuthKeyCreatorImplTest : public testing::Test {
  protected:
-  CryptAuthKeyCreatorImplTest() = default;
-  ~CryptAuthKeyCreatorImplTest() override = default;
+  DeviceSyncCryptAuthKeyCreatorImplTest() = default;
+  ~DeviceSyncCryptAuthKeyCreatorImplTest() override = default;
 
   void SetUp() override {
     fake_secure_message_delegate_factory_ =
@@ -116,10 +116,10 @@
 
   std::unique_ptr<CryptAuthKeyCreator> key_creator_;
 
-  DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyCreatorImplTest);
+  DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthKeyCreatorImplTest);
 };
 
-TEST_F(CryptAuthKeyCreatorImplTest, AsymmetricKeyCreation) {
+TEST_F(DeviceSyncCryptAuthKeyCreatorImplTest, AsymmetricKeyCreation) {
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
       keys_to_create = {
           {CryptAuthKeyBundle::Name::kUserKeyPair,
@@ -145,7 +145,7 @@
                      base::nullopt /* expected_client_ephemeral_dh */));
 }
 
-TEST_F(CryptAuthKeyCreatorImplTest, SymmetricKeyCreation) {
+TEST_F(DeviceSyncCryptAuthKeyCreatorImplTest, SymmetricKeyCreation) {
   base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
       keys_to_create = {
           {CryptAuthKeyBundle::Name::kUserKeyPair,
diff --git a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
index 749a9e1..246ddd1 100644
--- a/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_proof_computer_impl_unittest.cc
@@ -71,10 +71,10 @@
 
 }  // namespace
 
-class CryptAuthKeyProofComputerImplTest : public testing::Test {
+class DeviceSyncCryptAuthKeyProofComputerImplTest : public testing::Test {
  protected:
-  CryptAuthKeyProofComputerImplTest() = default;
-  ~CryptAuthKeyProofComputerImplTest() override = default;
+  DeviceSyncCryptAuthKeyProofComputerImplTest() = default;
+  ~DeviceSyncCryptAuthKeyProofComputerImplTest() override = default;
 
   void SetUp() override {
     fake_secure_message_delegate_factory_ =
@@ -143,10 +143,11 @@
 
   std::unique_ptr<CryptAuthKeyProofComputer> key_proof_computer_;
 
-  DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyProofComputerImplTest);
+  DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthKeyProofComputerImplTest);
 };
 
-TEST_F(CryptAuthKeyProofComputerImplTest, SuccessfulKeyProofComputation) {
+TEST_F(DeviceSyncCryptAuthKeyProofComputerImplTest,
+       SuccessfulKeyProofComputation) {
   std::vector<std::pair<CryptAuthKey, std::string>> key_payload_pairs = {
       {CryptAuthKey(kFakePublicKeyMaterial, kFakePrivateKeyMaterial,
                     CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256),
diff --git a/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
index 1386b17..0edbd17 100644
--- a/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_registry_impl_unittest.cc
@@ -13,11 +13,11 @@
 
 namespace device_sync {
 
-class CryptAuthKeyRegistryImplTest : public testing::Test {
+class DeviceSyncCryptAuthKeyRegistryImplTest : public testing::Test {
  protected:
-  CryptAuthKeyRegistryImplTest() = default;
+  DeviceSyncCryptAuthKeyRegistryImplTest() = default;
 
-  ~CryptAuthKeyRegistryImplTest() override = default;
+  ~DeviceSyncCryptAuthKeyRegistryImplTest() override = default;
 
   void SetUp() override {
     CryptAuthKeyRegistryImpl::RegisterPrefs(pref_service_.registry());
@@ -42,10 +42,10 @@
 
   std::unique_ptr<CryptAuthKeyRegistry> key_registry_;
 
-  DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyRegistryImplTest);
+  DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthKeyRegistryImplTest);
 };
 
-TEST_F(CryptAuthKeyRegistryImplTest, GetActiveKey_NoActiveKey) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, GetActiveKey_NoActiveKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
@@ -55,7 +55,7 @@
       key_registry()->GetActiveKey(CryptAuthKeyBundle::Name::kUserKeyPair));
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, GetActiveKey) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, GetActiveKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   CryptAuthKey asym_key("public-key", "private-key",
@@ -72,7 +72,7 @@
   EXPECT_EQ(asym_key, *key);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, AddKey) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, AddKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kActive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
@@ -117,7 +117,7 @@
   VerifyPrefValue(expected_dict);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, SetActiveKey) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, SetActiveKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   CryptAuthKey asym_key("public-key", "private-key",
@@ -149,7 +149,7 @@
   VerifyPrefValue(expected_dict);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, DeactivateKeys) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, DeactivateKeys) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   CryptAuthKey asym_key("public-key", "private-key",
@@ -176,7 +176,7 @@
   VerifyPrefValue(expected_dict);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, DeleteKey) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, DeleteKey) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   CryptAuthKey asym_key("public-key", "private-key",
@@ -208,7 +208,7 @@
   VerifyPrefValue(expected_dict);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, SetKeyDirective) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest, SetKeyDirective) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
@@ -237,7 +237,8 @@
   VerifyPrefValue(expected_dict);
 }
 
-TEST_F(CryptAuthKeyRegistryImplTest, ConstructorPopulatesBundlesUsingPref) {
+TEST_F(DeviceSyncCryptAuthKeyRegistryImplTest,
+       ConstructorPopulatesBundlesUsingPref) {
   CryptAuthKey sym_key("symmetric-key", CryptAuthKey::Status::kInactive,
                        cryptauthv2::KeyType::RAW256, "sym-handle");
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
diff --git a/chromeos/services/device_sync/cryptauth_key_unittest.cc b/chromeos/services/device_sync/cryptauth_key_unittest.cc
index a6ca54a..c16fe4c1 100644
--- a/chromeos/services/device_sync/cryptauth_key_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_key_unittest.cc
@@ -25,7 +25,7 @@
 
 }  // namespace
 
-TEST(CryptAuthKeyTest, CreateSymmetricKey) {
+TEST(DeviceSyncCryptAuthKeyTest, CreateSymmetricKey) {
   CryptAuthKey key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                    cryptauthv2::KeyType::RAW256);
 
@@ -42,7 +42,7 @@
   EXPECT_EQ(key_given_handle.handle(), kFakeHandle);
 }
 
-TEST(CryptAuthKeyTest, CreateAsymmetricKey) {
+TEST(DeviceSyncCryptAuthKeyTest, CreateAsymmetricKey) {
   CryptAuthKey key(kFakePublicKey, kFakePrivateKey,
                    CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
 
@@ -60,7 +60,7 @@
   EXPECT_EQ(key_given_handle.handle(), kFakeHandle);
 }
 
-TEST(CryptAuthKeyTest, SymmetricKeyAsDictionary) {
+TEST(DeviceSyncCryptAuthKeyTest, SymmetricKeyAsDictionary) {
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256, kFakeHandle);
 
@@ -73,7 +73,7 @@
   EXPECT_EQ(symmetric_key.AsSymmetricKeyDictionary(), dict);
 }
 
-TEST(CryptAuthKeyTest, AsymmetricKeyAsDictionary) {
+TEST(DeviceSyncCryptAuthKeyTest, AsymmetricKeyAsDictionary) {
   CryptAuthKey asymmetric_key(kFakePublicKey, kFakePrivateKey,
                               CryptAuthKey::Status::kActive,
                               cryptauthv2::KeyType::P256, kFakeHandle);
@@ -88,7 +88,7 @@
   EXPECT_EQ(asymmetric_key.AsAsymmetricKeyDictionary(), dict);
 }
 
-TEST(CryptAuthKeyTest, SymmetricKeyFromDictionary) {
+TEST(DeviceSyncCryptAuthKeyTest, SymmetricKeyFromDictionary) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -101,7 +101,7 @@
                                cryptauthv2::KeyType::RAW256, kFakeHandle));
 }
 
-TEST(CryptAuthKeyTest, AsymmetricKeyFromDictionary) {
+TEST(DeviceSyncCryptAuthKeyTest, AsymmetricKeyFromDictionary) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -116,7 +116,7 @@
                                cryptauthv2::KeyType::P256, kFakeHandle));
 }
 
-TEST(CryptAuthKeyTest, KeyFromDictionary_MissingHandle) {
+TEST(DeviceSyncCryptAuthKeyTest, KeyFromDictionary_MissingHandle) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::RAW256));
@@ -125,7 +125,7 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, KeyFromDictionary_MissingStatus) {
+TEST(DeviceSyncCryptAuthKeyTest, KeyFromDictionary_MissingStatus) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("type", base::Value(cryptauthv2::KeyType::RAW256));
@@ -134,7 +134,7 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, KeyFromDictionary_MissingType) {
+TEST(DeviceSyncCryptAuthKeyTest, KeyFromDictionary_MissingType) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -143,7 +143,8 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, SymmetricKeyFromDictionary_MissingSymmetricKey) {
+TEST(DeviceSyncCryptAuthKeyTest,
+     SymmetricKeyFromDictionary_MissingSymmetricKey) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -152,7 +153,7 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, AsymmetricKeyFromDictionary_MissingPublicKey) {
+TEST(DeviceSyncCryptAuthKeyTest, AsymmetricKeyFromDictionary_MissingPublicKey) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -162,7 +163,8 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, AsymmetricKeyFromDictionary_MissingPrivateKey) {
+TEST(DeviceSyncCryptAuthKeyTest,
+     AsymmetricKeyFromDictionary_MissingPrivateKey) {
   base::Value dict(base::Value::Type::DICTIONARY);
   dict.SetKey("handle", base::Value(kFakeHandle));
   dict.SetKey("status", base::Value(CryptAuthKey::Status::kActive));
@@ -172,7 +174,7 @@
   EXPECT_FALSE(CryptAuthKey::FromDictionary(dict));
 }
 
-TEST(CryptAuthKeyTest, Equality) {
+TEST(DeviceSyncCryptAuthKeyTest, Equality) {
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256);
   CryptAuthKey asymmetric_key(kFakePublicKey, kFakePrivateKey,
@@ -187,7 +189,7 @@
                                          cryptauthv2::KeyType::P256));
 }
 
-TEST(CryptAuthKeyTest, NotEquality) {
+TEST(DeviceSyncCryptAuthKeyTest, NotEquality) {
   CryptAuthKey symmetric_key(kFakeSymmetricKey, CryptAuthKey::Status::kActive,
                              cryptauthv2::KeyType::RAW256);
   CryptAuthKey asymmetric_key(kFakePublicKey, kFakePrivateKey,
diff --git a/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
index ed7364a..9fc479e 100644
--- a/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
+++ b/chromeos/services/device_sync/cryptauth_v2_enroller_impl_unittest.cc
@@ -347,11 +347,11 @@
 
 }  // namespace
 
-class CryptAuthV2EnrollerImplTest
+class DeviceSyncCryptAuthV2EnrollerImplTest
     : public testing::Test,
       public MockCryptAuthClientFactory::Observer {
  protected:
-  CryptAuthV2EnrollerImplTest()
+  DeviceSyncCryptAuthV2EnrollerImplTest()
       : client_factory_(std::make_unique<MockCryptAuthClientFactory>(
             MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS)),
         fake_cryptauth_key_creator_factory_(
@@ -365,7 +365,7 @@
     client_factory_->AddObserver(this);
   }
 
-  ~CryptAuthV2EnrollerImplTest() override {
+  ~DeviceSyncCryptAuthV2EnrollerImplTest() override {
     client_factory_->RemoveObserver(this);
   }
 
@@ -392,11 +392,12 @@
   // MockCryptAuthClientFactory::Observer:
   void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
     ON_CALL(*client, SyncKeys(testing::_, testing::_, testing::_))
-        .WillByDefault(Invoke(this, &CryptAuthV2EnrollerImplTest::OnSyncKeys));
+        .WillByDefault(
+            Invoke(this, &DeviceSyncCryptAuthV2EnrollerImplTest::OnSyncKeys));
 
     ON_CALL(*client, EnrollKeys(testing::_, testing::_, testing::_))
         .WillByDefault(
-            Invoke(this, &CryptAuthV2EnrollerImplTest::OnEnrollKeys));
+            Invoke(this, &DeviceSyncCryptAuthV2EnrollerImplTest::OnEnrollKeys));
 
     ON_CALL(*client, GetAccessTokenUsed())
         .WillByDefault(testing::Return(kAccessTokenUsed));
@@ -408,8 +409,9 @@
                       client_directive_policy_reference) {
     enroller()->Enroll(
         client_metadata, client_app_metadata, client_directive_policy_reference,
-        base::BindOnce(&CryptAuthV2EnrollerImplTest::OnEnrollmentComplete,
-                       base::Unretained(this)));
+        base::BindOnce(
+            &DeviceSyncCryptAuthV2EnrollerImplTest::OnEnrollmentComplete,
+            base::Unretained(this)));
   }
 
   void OnSyncKeys(const SyncKeysRequest& request,
@@ -554,10 +556,10 @@
 
   std::unique_ptr<CryptAuthV2Enroller> enroller_;
 
-  DISALLOW_COPY_AND_ASSIGN(CryptAuthV2EnrollerImplTest);
+  DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthV2EnrollerImplTest);
 };
 
-TEST_F(CryptAuthV2EnrollerImplTest, SuccessfulEnrollment) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, SuccessfulEnrollment) {
   // Seed key registry.
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  kOldActiveAsymmetricKey);
@@ -660,7 +662,8 @@
             *key_registry()->GetKeyBundle(bundle_name));
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, SuccessfulEnrollment_NoKeysCreated) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       SuccessfulEnrollment_NoKeysCreated) {
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  kOldActiveAsymmetricKey);
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
@@ -696,7 +699,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_ServerOverloaded) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_ServerOverloaded) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -711,7 +714,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_MissingSessionId) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_MissingSessionId) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -727,7 +730,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_MissingClientDirective) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_MissingClientDirective) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -743,7 +746,8 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_InvalidSyncSingleKeyResponsesSize) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       Failure_InvalidSyncSingleKeyResponsesSize) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -759,7 +763,7 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_InvalidKeyActions_Size) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_InvalidKeyActions_Size) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -778,7 +782,8 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_InvalidKeyActions_NoActiveKey) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       Failure_InvalidKeyActions_NoActiveKey) {
   key_registry()->AddEnrolledKey(CryptAuthKeyBundle::Name::kUserKeyPair,
                                  kOldActiveAsymmetricKey);
 
@@ -803,7 +808,7 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest,
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_InvalidKeyCreationInstructions_UnsupportedKeyType) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
@@ -824,7 +829,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest,
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_InvalidKeyCreationInstructions_NoServerDiffieHellman) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
@@ -847,7 +852,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_SyncKeysApiCall) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_SyncKeysApiCall) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -860,7 +865,7 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_EnrollKeysApiCall) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest, Failure_EnrollKeysApiCall) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -889,7 +894,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest,
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_Timeout_WaitingForSyncKeysResponse) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
@@ -905,7 +910,8 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest, Failure_Timeout_WaitingForKeyCreation) {
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
+       Failure_Timeout_WaitingForKeyCreation) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
 
@@ -928,7 +934,7 @@
             enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest,
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_Timeout_WaitingForKeyProofComputation) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
@@ -960,7 +966,7 @@
       enrollment_result());
 }
 
-TEST_F(CryptAuthV2EnrollerImplTest,
+TEST_F(DeviceSyncCryptAuthV2EnrollerImplTest,
        Failure_Timeout_WaitingForEnrollKeysResponse) {
   CallEnroll(GetSampleClientMetadata(), GetSampleClientAppMetadata(),
              GetSamplePreviousClientDirectivePolicyReference());
diff --git a/chromeos/services/device_sync/persistent_enrollment_scheduler.cc b/chromeos/services/device_sync/persistent_enrollment_scheduler.cc
index a05cda19..2a3692f 100644
--- a/chromeos/services/device_sync/persistent_enrollment_scheduler.cc
+++ b/chromeos/services/device_sync/persistent_enrollment_scheduler.cc
@@ -32,7 +32,13 @@
 // retry_period_millis sent by CryptAuth in SyncKeysResponse.
 constexpr base::TimeDelta kDefaultRetryPeriod = base::TimeDelta::FromHours(12);
 
-// The default number of immediate retries after a failed enrollment attempt.
+// The time to wait before an "immediate" retry attempt after a failed
+// enrollment attempt. Note: SyncKeys requests are throttled by CryptAuth if
+// more than one is sent within a five-minute window.
+constexpr base::TimeDelta kImmediateRetryDelay =
+    base::TimeDelta::FromMinutes(5);
+
+// The default number of "immediate" retries after a failed enrollment attempt.
 // Superseded by the ClientDirective's retry_attempts sent by CryptAuth in the
 // SyncKeysResponse.
 const int kDefaultMaxImmediateRetries = 3;
@@ -264,7 +270,7 @@
     return GetRefreshPeriod();
 
   if (num_consecutive_failures <= (size_t)client_directive_.retry_attempts())
-    return base::TimeDelta::FromMilliseconds(0);
+    return kImmediateRetryDelay;
 
   return base::TimeDelta::FromMilliseconds(
       client_directive_.retry_period_millis());
diff --git a/chromeos/services/device_sync/persistent_enrollment_scheduler_unittest.cc b/chromeos/services/device_sync/persistent_enrollment_scheduler_unittest.cc
index aa9cc1f4..fb989d8 100644
--- a/chromeos/services/device_sync/persistent_enrollment_scheduler_unittest.cc
+++ b/chromeos/services/device_sync/persistent_enrollment_scheduler_unittest.cc
@@ -30,6 +30,8 @@
 int kFakePolicyVersion = 100;
 constexpr base::TimeDelta kFakeRefreshPeriod = base::TimeDelta::FromDays(100);
 constexpr base::TimeDelta kFakeRetryPeriod = base::TimeDelta::FromHours(100);
+constexpr base::TimeDelta kFakeImmediateRetryDelay =
+    base::TimeDelta::FromMinutes(5);
 const int kFakeMaxImmediateRetries = 2;
 
 // The time set on the scheduler's clock during set-up.
@@ -272,7 +274,7 @@
     EXPECT_EQ(base::TimeDelta::FromMilliseconds(
                   fake_client_directive().checkin_delay_millis()),
               scheduler()->GetRefreshPeriod());
-    EXPECT_EQ(base::TimeDelta::FromMilliseconds(0),
+    EXPECT_EQ(kFakeImmediateRetryDelay,
               scheduler()->GetTimeToNextEnrollmentRequest());
     VerifyLastEnrollmentAttemptTimePref(kFakeTimeNow);
   }
@@ -344,7 +346,7 @@
   CreateScheduler();
 
   EXPECT_EQ(1u, scheduler()->GetNumConsecutiveFailures());
-  EXPECT_EQ(base::TimeDelta::FromMilliseconds(0),
+  EXPECT_EQ(kFakeImmediateRetryDelay,
             scheduler()->GetTimeToNextEnrollmentRequest());
   VerifyLastEnrollmentAttemptTimePref(kFakeTimeLaterBeforeRetryPeriod);
 }
diff --git a/chromeos/services/device_sync/public/cpp/gcm_constants.cc b/chromeos/services/device_sync/public/cpp/gcm_constants.cc
index 505e332..39f8fcc 100644
--- a/chromeos/services/device_sync/public/cpp/gcm_constants.cc
+++ b/chromeos/services/device_sync/public/cpp/gcm_constants.cc
@@ -10,7 +10,7 @@
 
 const char kCryptAuthGcmAppId[] = "com.google.chrome.cryptauth";
 const char kCryptAuthGcmSenderId[] = "381449029288";
-const char kCryptAuthGcmProjectId[] = "433637338589";
+const char kCryptAuthGcmInstanceIdAuthorizedEntity[] = "16502139086";
 
 }  // namespace device_sync
 
diff --git a/chromeos/services/device_sync/public/cpp/gcm_constants.h b/chromeos/services/device_sync/public/cpp/gcm_constants.h
index 540d2a3..e4ec664 100644
--- a/chromeos/services/device_sync/public/cpp/gcm_constants.h
+++ b/chromeos/services/device_sync/public/cpp/gcm_constants.h
@@ -12,7 +12,7 @@
 // ID constants used in GCM for CryptAuth-related calls.
 extern const char kCryptAuthGcmAppId[];
 extern const char kCryptAuthGcmSenderId[];
-extern const char kCryptAuthGcmProjectId[];
+extern const char kCryptAuthGcmInstanceIdAuthorizedEntity[];
 
 }  // namespace device_sync
 
diff --git a/components/arc/timer/arc_timer_bridge.cc b/components/arc/timer/arc_timer_bridge.cc
index 54143c1..a545c7b 100644
--- a/components/arc/timer/arc_timer_bridge.cc
+++ b/components/arc/timer/arc_timer_bridge.cc
@@ -128,6 +128,7 @@
   // Convert mojo arguments to D-Bus arguments required by powerd to create
   // timers.
   std::vector<std::pair<clockid_t, base::ScopedFD>> requests;
+  std::vector<clockid_t> clock_ids;
   for (auto& request : arc_timer_requests) {
     clockid_t clock_id = request->clock_id;
     base::ScopedFD expiration_fd =
@@ -138,11 +139,14 @@
       return;
     }
     requests.emplace_back(clock_id, std::move(expiration_fd));
+    clock_ids.emplace_back(clock_id);
   }
-  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->DeleteArcTimers(
-      kTag, base::BindOnce(&ArcTimerBridge::OnDeleteBeforeCreateArcTimers,
-                           weak_ptr_factory_.GetWeakPtr(), std::move(requests),
-                           std::move(callback)));
+
+  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->CreateArcTimers(
+      kTag, std::move(requests),
+      base::BindOnce(&ArcTimerBridge::OnCreateArcTimers,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(clock_ids),
+                     std::move(callback)));
 }
 
 void ArcTimerBridge::StartTimer(clockid_t clock_id,
@@ -177,36 +181,15 @@
   timer_ids_.clear();
 }
 
-void ArcTimerBridge::OnDeleteBeforeCreateArcTimers(
-    std::vector<std::pair<clockid_t, base::ScopedFD>>
-        create_arc_timers_requests,
-    CreateTimersCallback callback,
-    bool result) {
-  if (!result) {
-    LOG(ERROR) << "Delete timers before create failed";
-    std::move(callback).Run(mojom::ArcTimerResult::FAILURE);
-    return;
-  }
-
-  DVLOG(1) << "Delete before create timers succeeded";
-  // If the delete call succeeded then delete any timer ids stored and make a
-  // create timers call.
-  timer_ids_.clear();
-  std::vector<clockid_t> clock_ids;
-  for (const auto& request : create_arc_timers_requests)
-    clock_ids.emplace_back(request.first);
-
-  chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->CreateArcTimers(
-      kTag, std::move(create_arc_timers_requests),
-      base::BindOnce(&ArcTimerBridge::OnCreateArcTimers,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(clock_ids),
-                     std::move(callback)));
-}
-
 void ArcTimerBridge::OnCreateArcTimers(
     std::vector<clockid_t> clock_ids,
     CreateTimersCallback callback,
     base::Optional<std::vector<TimerId>> timer_ids) {
+  // Any old timers associated with the same tag are always cleared by the API
+  // regardless of the new timers being created successfully or not. Clear the
+  // cached timer ids in that case.
+  timer_ids_.clear();
+
   // The API returns a list of timer ids corresponding to each clock in
   // |clock_ids|.
   if (!timer_ids.has_value()) {
diff --git a/components/arc/timer/arc_timer_bridge.h b/components/arc/timer/arc_timer_bridge.h
index 162941a..790e4fe 100644
--- a/components/arc/timer/arc_timer_bridge.h
+++ b/components/arc/timer/arc_timer_bridge.h
@@ -69,13 +69,6 @@
   // Callback for (powerd API) call made in |DeleteArcTimers|.
   void OnDeleteArcTimers(bool result);
 
-  // Callback for delete timers (powerd API) call made in |CreateTimers|.
-  void OnDeleteBeforeCreateArcTimers(
-      std::vector<std::pair<clockid_t, base::ScopedFD>>
-          create_arc_timers_requests,
-      CreateTimersCallback callback,
-      bool result);
-
   // Callback for powerd's D-Bus API called in |CreateTimers|.
   void OnCreateArcTimers(std::vector<clockid_t> clock_ids,
                          CreateTimersCallback callback,
diff --git a/components/arc/timer/arc_timer_bridge_unittest.cc b/components/arc/timer/arc_timer_bridge_unittest.cc
index d91abf1..ef95f1c 100644
--- a/components/arc/timer/arc_timer_bridge_unittest.cc
+++ b/components/arc/timer/arc_timer_bridge_unittest.cc
@@ -285,8 +285,8 @@
 TEST_F(ArcTimerTest, CheckMultipleCreateTimersTest) {
   std::vector<clockid_t> clocks = {CLOCK_REALTIME_ALARM};
   EXPECT_TRUE(CreateTimers(clocks));
-  // The create implementation calls powerd's delete API before calling create.
-  // The second create should thus succeed.
+  // The power manager implicitly deletes old timers associated with a tag
+  // during a create call. Thus, consecutive create calls should succeed.
   EXPECT_TRUE(CreateTimers(clocks));
 }
 
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 65f1180..b88ad9a 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -592,6 +592,14 @@
 }
 
 // static
+void AutofillMetrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
+    SaveTypeMetric metric) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "Autofill.StrikeDatabase.LocalCardMigrationNotOfferedDueToMaxStrikes",
+      metric);
+}
+
+// static
 void AutofillMetrics::LogUploadDisallowedForNetworkMetric(
     const std::string& network) {
   UploadDisallowedForNetworkMetric metric;
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 427ab7fe..5803d9f7 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -861,6 +861,11 @@
   static void LogCreditCardSaveNotOfferedDueToMaxStrikesMetric(
       SaveTypeMetric metric);
 
+  // When local card migration is not offered due to max strike limit reached,
+  // logs the occurrence.
+  static void LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
+      SaveTypeMetric metric);
+
   // When credit card upload is disallowed for a particular network, logs which
   // network was blocked.
   static void LogUploadDisallowedForNetworkMetric(const std::string& network);
diff --git a/components/autofill/core/browser/credit_card_save_manager.cc b/components/autofill/core/browser/credit_card_save_manager.cc
index aef0d40..84e8922 100644
--- a/components/autofill/core/browser/credit_card_save_manager.cc
+++ b/components/autofill/core/browser/credit_card_save_manager.cc
@@ -339,7 +339,9 @@
           /*is_local=*/false,
           GetCreditCardSaveStrikeDatabase()->GetStrikes(
               base::UTF16ToUTF8(upload_request_.card.LastFourDigits())));
-      // Clear all strikes for this card, in case it is later removed.
+
+      // Clear all CreditCardSave strikes for this card, in case it is later
+      // removed.
       GetCreditCardSaveStrikeDatabase()->ClearStrikes(
           base::UTF16ToUTF8(upload_request_.card.LastFourDigits()));
     } else if (base::FeatureList::IsEnabled(
@@ -388,11 +390,22 @@
 
 CreditCardSaveStrikeDatabase*
 CreditCardSaveManager::GetCreditCardSaveStrikeDatabase() {
-  if (strike_database_.get() == nullptr) {
-    strike_database_ = std::make_unique<CreditCardSaveStrikeDatabase>(
-        CreditCardSaveStrikeDatabase(client_->GetStrikeDatabase()));
+  if (credit_card_save_strike_database_.get() == nullptr) {
+    credit_card_save_strike_database_ =
+        std::make_unique<CreditCardSaveStrikeDatabase>(
+            CreditCardSaveStrikeDatabase(client_->GetStrikeDatabase()));
   }
-  return strike_database_.get();
+  return credit_card_save_strike_database_.get();
+}
+
+LocalCardMigrationStrikeDatabase*
+CreditCardSaveManager::GetLocalCardMigrationStrikeDatabase() {
+  if (local_card_migration_strike_database_.get() == nullptr) {
+    local_card_migration_strike_database_ =
+        std::make_unique<LocalCardMigrationStrikeDatabase>(
+            LocalCardMigrationStrikeDatabase(client_->GetStrikeDatabase()));
+  }
+  return local_card_migration_strike_database_.get();
 }
 
 void CreditCardSaveManager::OnDidGetStrikesForLocalSave(const int num_strikes) {
@@ -554,12 +567,20 @@
             /*is_local=*/true);
       if (base::FeatureList::IsEnabled(
               features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
-        // Log how many strikes the card had when it was saved.
+        // Log how many CreditCardSave strikes the card had when it was saved.
         LogStrikesPresentWhenCardSaved(
             /*is_local=*/true,
             GetCreditCardSaveStrikeDatabase()->GetStrikes(base::UTF16ToUTF8(
                 local_card_save_candidate_.LastFourDigits())));
-        // Clear all strikes for this card, in case it is later removed.
+        if (base::FeatureList::IsEnabled(
+                features::kAutofillLocalCardMigrationUsesStrikeSystemV2)) {
+          GetLocalCardMigrationStrikeDatabase()->RemoveStrikes(
+              LocalCardMigrationStrikeDatabase::
+                  kStrikesToRemoveWhenLocalCardAdded);
+        }
+
+        // Clear all CreditCardSave strikes for this card, in case it is later
+        // removed.
         GetCreditCardSaveStrikeDatabase()->ClearStrikes(
             base::UTF16ToUTF8(local_card_save_candidate_.LastFourDigits()));
       } else if (base::FeatureList::IsEnabled(
diff --git a/components/autofill/core/browser/credit_card_save_manager.h b/components/autofill/core/browser/credit_card_save_manager.h
index 45a1b0f5..f6fd234 100644
--- a/components/autofill/core/browser/credit_card_save_manager.h
+++ b/components/autofill/core/browser/credit_card_save_manager.h
@@ -18,6 +18,7 @@
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/credit_card_save_strike_database.h"
 #include "components/autofill/core/browser/form_structure.h"
+#include "components/autofill/core/browser/local_card_migration_strike_database.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "url/origin.h"
@@ -139,6 +140,9 @@
   // Returns the CreditCardSaveStrikeDatabase for |client_|.
   CreditCardSaveStrikeDatabase* GetCreditCardSaveStrikeDatabase();
 
+  // Returns the GetLocalCardMigrationStrikeDatabase for |client_|.
+  LocalCardMigrationStrikeDatabase* GetLocalCardMigrationStrikeDatabase();
+
   // Sets |show_save_prompt| and moves forward with offering credit card local
   // save.
   void OnDidGetStrikesForLocalSave(const int num_strikes);
@@ -325,7 +329,11 @@
   // The returned legal message from a GetUploadDetails call to Google Payments.
   std::unique_ptr<base::DictionaryValue> legal_message_;
 
-  std::unique_ptr<CreditCardSaveStrikeDatabase> strike_database_;
+  std::unique_ptr<CreditCardSaveStrikeDatabase>
+      credit_card_save_strike_database_;
+
+  std::unique_ptr<LocalCardMigrationStrikeDatabase>
+      local_card_migration_strike_database_;
 
   // May be null.
   ObserverForTest* observer_for_testing_ = nullptr;
diff --git a/components/autofill/core/browser/credit_card_save_manager_unittest.cc b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
index d01e71d..af7d56d8 100644
--- a/components/autofill/core/browser/credit_card_save_manager_unittest.cc
+++ b/components/autofill/core/browser/credit_card_save_manager_unittest.cc
@@ -5397,4 +5397,116 @@
   UserHasAcceptedUpload({});
 }
 
+// Tests that 2 LocalCardMigrationStrikes are removed when cards are saved
+// locally.
+TEST_F(CreditCardSaveManagerTest,
+       LocalCreditCard_LocalCardMigrationStrikesRemovedOnLocalSave) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database =
+      LocalCardMigrationStrikeDatabase(strike_database_);
+
+  // Start with 3 strikes in |local_card_migration_strike_database|.
+  local_card_migration_strike_database.AddStrikes(3);
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 3);
+
+  credit_card_save_manager_->SetCreditCardUploadEnabled(false);
+
+  // Create, fill and submit an address form in order to establish a recent
+  // profile which can be selected for the upload request.
+  FormData address_form;
+  test::CreateTestAddressFormData(&address_form);
+  FormsSeen(std::vector<FormData>(1, address_form));
+  ExpectUniqueFillableFormParsedUkm();
+
+  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
+  FormSubmitted(address_form);
+
+  // Set up our credit card form data with credit card first and last name
+  // fields.
+  FormData credit_card_form;
+  CreateTestCreditCardFormData(&credit_card_form, /*is_https=*/true,
+                               /*use_month_type=*/false, /*split_names=*/true);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  // Edit the data, and submit.
+  credit_card_form.fields[0].value = ASCIIToUTF16("Flo");
+  credit_card_form.fields[1].value = ASCIIToUTF16("Master");
+  credit_card_form.fields[2].value = ASCIIToUTF16("4111111111111111");
+  credit_card_form.fields[3].value = ASCIIToUTF16(NextMonth());
+  credit_card_form.fields[4].value = ASCIIToUTF16(NextYear());
+  credit_card_form.fields[5].value = ASCIIToUTF16("123");
+
+  base::HistogramTester histogram_tester;
+
+  FormSubmitted(credit_card_form);
+  EXPECT_TRUE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
+  EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded());
+
+  // 2 strikes should be removed when card was saved locally.
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 1);
+}
+
+// Tests that no LocalCardMigrationStrikes get removed due to cards being
+// uploaded.
+TEST_F(CreditCardSaveManagerTest,
+       UploadCreditCard_NoLocalSaveMigrationStrikesRemovedOnUpload) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database =
+      LocalCardMigrationStrikeDatabase(strike_database_);
+
+  // Start with 3 strikes in |local_card_migration_strike_database|.
+  local_card_migration_strike_database.AddStrikes(3);
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 3);
+
+  credit_card_save_manager_->SetCreditCardUploadEnabled(true);
+
+  // Create, fill and submit an address form in order to establish a recent
+  // profile which can be selected for the upload request.
+  FormData address_form;
+  test::CreateTestAddressFormData(&address_form);
+  FormsSeen(std::vector<FormData>(1, address_form));
+  ExpectUniqueFillableFormParsedUkm();
+
+  ManuallyFillAddressForm("Flo", "Master", "77401", "US", &address_form);
+  FormSubmitted(address_form);
+
+  // Set up our credit card form data with credit card first and last name
+  // fields.
+  FormData credit_card_form;
+  CreateTestCreditCardFormData(&credit_card_form, /*is_https=*/true,
+                               /*use_month_type=*/false, /*split_names=*/true);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  // Edit the data, and submit.
+  credit_card_form.fields[0].value = ASCIIToUTF16("Flo");
+  credit_card_form.fields[1].value = ASCIIToUTF16("Master");
+  credit_card_form.fields[2].value = ASCIIToUTF16("4111111111111111");
+  credit_card_form.fields[3].value = ASCIIToUTF16(NextMonth());
+  credit_card_form.fields[4].value = ASCIIToUTF16(NextYear());
+  credit_card_form.fields[5].value = ASCIIToUTF16("123");
+
+  base::HistogramTester histogram_tester;
+
+  FormSubmitted(credit_card_form);
+  EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled());
+  EXPECT_TRUE(credit_card_save_manager_->CreditCardWasUploaded());
+
+  // Strike count shouldn't change.
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 3);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/local_card_migration_manager.cc b/components/autofill/core/browser/local_card_migration_manager.cc
index bca87d3..ccb9c3d7 100644
--- a/components/autofill/core/browser/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/local_card_migration_manager.cc
@@ -40,7 +40,17 @@
       payments_client_(payments_client),
       app_locale_(app_locale),
       personal_data_manager_(personal_data_manager),
-      weak_ptr_factory_(this) {}
+      weak_ptr_factory_(this) {
+  // This is to initialize StrikeDatabase is if it hasn't been already, so that
+  // its cache would be loaded and ready to use when the first LCMM is created.
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
+    // Only init when |kAutofillSaveCreditCardUsesStrikeSystemV2| is enabled. If
+    // flag is off and LegacyStrikeDatabase instead of StrikeDatabase is used,
+    // this init will cause failure on GetStrikes().
+    client_->GetStrikeDatabase();
+  }
+}
 
 LocalCardMigrationManager::~LocalCardMigrationManager() {}
 
@@ -63,9 +73,28 @@
   if (!IsCreditCardMigrationEnabled())
     return false;
 
-  // Don't show the the prompt if user cancelled/rejected previously.
-  if (prefs::IsLocalCardMigrationPromptPreviouslyCancelled(client_->GetPrefs()))
+  // Don't show the prompt if max strike count was reached.
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillSaveCreditCardUsesStrikeSystemV2) &&
+      base::FeatureList::IsEnabled(
+          features::kAutofillLocalCardMigrationUsesStrikeSystemV2) &&
+      GetLocalCardMigrationStrikeDatabase()->IsMaxStrikesLimitReached()) {
+    switch (imported_credit_card_record_type) {
+      case FormDataImporter::ImportedCreditCardRecordType::LOCAL_CARD:
+        AutofillMetrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
+            AutofillMetrics::SaveTypeMetric::LOCAL);
+        break;
+      case FormDataImporter::ImportedCreditCardRecordType::SERVER_CARD:
+        AutofillMetrics::LogLocalCardMigrationNotOfferedDueToMaxStrikesMetric(
+            AutofillMetrics::SaveTypeMetric::SERVER);
+        break;
+    }
     return false;
+  } else if (prefs::IsLocalCardMigrationPromptPreviouslyCancelled(
+                 client_->GetPrefs())) {
+    // Don't show the the prompt if user cancelled/rejected previously.
+    return false;
+  }
 
   // Fetch all migratable credit cards and store in |migratable_credit_cards_|.
   GetMigratableCreditCards();
@@ -120,6 +149,17 @@
   user_accepted_main_migration_dialog_ = true;
   AutofillMetrics::LogLocalCardMigrationPromptMetric(
       local_card_migration_origin_, AutofillMetrics::MAIN_DIALOG_ACCEPTED);
+
+  // If there are cards which aren't selected, add 3 strikes to
+  // LocalCardMigrationStrikeDatabase.
+  if (base::FeatureList::IsEnabled(
+          features::kAutofillSaveCreditCardUsesStrikeSystemV2) &&
+      (selected_card_guids.size() < migratable_credit_cards_.size())) {
+    GetLocalCardMigrationStrikeDatabase()->AddStrikes(
+        LocalCardMigrationStrikeDatabase::
+            kStrikesToAddWhenCardsDeselectedAtMigration);
+  }
+
   // Update the |migratable_credit_cards_| with the |selected_card_guids|. This
   // will remove any card from |migratable_credit_cards_| of which the GUID is
   // not in |selected_card_guids|.
@@ -290,6 +330,16 @@
   user_accepted_main_migration_dialog_ = false;
 }
 
+LocalCardMigrationStrikeDatabase*
+LocalCardMigrationManager::GetLocalCardMigrationStrikeDatabase() {
+  if (local_card_migration_strike_database_.get() == nullptr) {
+    local_card_migration_strike_database_ =
+        std::make_unique<LocalCardMigrationStrikeDatabase>(
+            LocalCardMigrationStrikeDatabase(client_->GetStrikeDatabase()));
+  }
+  return local_card_migration_strike_database_.get();
+}
+
 // Pops up a larger, modal dialog showing the local cards to be uploaded. Pass
 // the reference of vector<MigratableCreditCard> and the callback function is
 // OnUserAcceptedMainMigrationDialog(). Can be called when user agrees to
diff --git a/components/autofill/core/browser/local_card_migration_manager.h b/components/autofill/core/browser/local_card_migration_manager.h
index dc2fa329..a040d1f 100644
--- a/components/autofill/core/browser/local_card_migration_manager.h
+++ b/components/autofill/core/browser/local_card_migration_manager.h
@@ -12,6 +12,7 @@
 #include "base/strings/string16.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/local_card_migration_strike_database.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
 
 namespace autofill {
@@ -161,6 +162,9 @@
   FRIEND_TEST_ALL_PREFIXES(LocalCardMigrationManagerTest,
                            MigrateCreditCard_ToggleIsChosen);
 
+  // Returns the LocalCardMigrationStrikeDatabase for |client_|.
+  LocalCardMigrationStrikeDatabase* GetLocalCardMigrationStrikeDatabase();
+
   // Pops up a larger, modal dialog showing the local cards to be uploaded.
   void ShowMainMigrationDialog();
 
@@ -206,6 +210,9 @@
   // Initialized only during tests.
   ObserverForTest* observer_for_testing_ = nullptr;
 
+  std::unique_ptr<LocalCardMigrationStrikeDatabase>
+      local_card_migration_strike_database_;
+
   base::WeakPtrFactory<LocalCardMigrationManager> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LocalCardMigrationManager);
diff --git a/components/autofill/core/browser/local_card_migration_manager_unittest.cc b/components/autofill/core/browser/local_card_migration_manager_unittest.cc
index 6dd9308..983d810 100644
--- a/components/autofill/core/browser/local_card_migration_manager_unittest.cc
+++ b/components/autofill/core/browser/local_card_migration_manager_unittest.cc
@@ -81,6 +81,10 @@
     local_card_migration_manager_ = new TestLocalCardMigrationManager(
         autofill_driver_.get(), &autofill_client_, payments_client_,
         &personal_data_);
+    std::unique_ptr<TestStrikeDatabase> test_strike_database =
+        std::make_unique<TestStrikeDatabase>();
+    strike_database_ = test_strike_database.get();
+    autofill_client_.set_test_strike_database(std::move(test_strike_database));
     autofill::TestFormDataImporter* test_form_data_importer =
         new TestFormDataImporter(
             &autofill_client_, payments_client_,
@@ -177,6 +181,8 @@
   MockAutocompleteHistoryManager autocomplete_history_manager_;
   syncer::TestSyncService sync_service_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  // Ends up getting owned (and destroyed) by TestAutofillClient:
+  TestStrikeDatabase* strike_database_;
   // Ends up getting owned (and destroyed) by TestFormDataImporter:
   TestCreditCardSaveManager* credit_card_save_manager_;
   // Ends up getting owned (and destroyed) by TestFormDataImporter:
@@ -1038,4 +1044,141 @@
   EXPECT_FALSE(personal_data_.GetCreditCardWithGUID("guid1"));
 }
 
+// Use one local card with more valid local cards available, don't show prompt
+// if max strikes reached.
+TEST_F(LocalCardMigrationManagerTest,
+       MigrateLocalCreditCard_MaxStrikesReached) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database =
+      LocalCardMigrationStrikeDatabase(strike_database_);
+  local_card_migration_strike_database.AddStrikes(7);
+
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 7);
+
+  // Set the billing_customer_number Priority Preference to designate
+  // existence of a Payments account.
+  autofill_client_.GetPrefs()->SetDouble(prefs::kAutofillBillingCustomerNumber,
+                                         12345);
+  // Add a local credit card whose |TypeAndLastFourDigits| matches what we will
+  // enter below.
+  AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "1", "guid1");
+  // Add another local credit card, so it will trigger migration.
+  AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
+                     test::NextYear().c_str(), "1", "guid2");
+
+  // Set up our credit card form data.
+  FormData credit_card_form;
+  test::CreateTestCreditCardFormData(&credit_card_form, true, false);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  base::HistogramTester histogram_tester;
+  // Edit the data, and submit.
+  EditCreditCardFrom(credit_card_form, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "123");
+  FormSubmitted(credit_card_form);
+
+  // Local card migration not triggered since max strikes have been reached.
+  EXPECT_FALSE(local_card_migration_manager_->LocalCardMigrationWasTriggered());
+
+  // Verify that the correct histogram entry was logged.
+  histogram_tester.ExpectBucketCount(
+      "Autofill.StrikeDatabase.LocalCardMigrationNotOfferedDueToMaxStrikes",
+      AutofillMetrics::SaveTypeMetric::LOCAL, 1);
+}
+
+// Use one server card with more valid local cards available, don't show prompt
+// if max strikes reached.
+TEST_F(LocalCardMigrationManagerTest,
+       MigrateServerCreditCard_MaxStrikesReached) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database =
+      LocalCardMigrationStrikeDatabase(strike_database_);
+  local_card_migration_strike_database.AddStrikes(7);
+
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 7);
+
+  // Set the billing_customer_number Priority Preference to designate
+  // existence of a Payments account.
+  autofill_client_.GetPrefs()->SetDouble(prefs::kAutofillBillingCustomerNumber,
+                                         12345);
+
+  // Add a masked server credit card whose |TypeAndLastFourDigits| matches what
+  // we will enter below.
+  CreditCard credit_card(CreditCard::MASKED_SERVER_CARD, "a123");
+  test::SetCreditCardInfo(&credit_card, "Flo Master", "1111", "11",
+                          test::NextYear().c_str(), "1");
+  credit_card.SetNetworkForMaskedCard(kVisaCard);
+  personal_data_.AddServerCreditCard(credit_card);
+  // Add one valid local credit card, so it will trigger migration
+  AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
+                     test::NextYear().c_str(), "1", "guid1");
+
+  // Set up our credit card form data.
+  FormData credit_card_form;
+  test::CreateTestCreditCardFormData(&credit_card_form, true, false);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  base::HistogramTester histogram_tester;
+  // Edit the data, and submit.
+  EditCreditCardFrom(credit_card_form, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "123");
+  FormSubmitted(credit_card_form);
+
+  // Local card migration not triggered since max strikes have been reached.
+  EXPECT_FALSE(local_card_migration_manager_->LocalCardMigrationWasTriggered());
+
+  // Verify that the correct histogram entry was logged.
+  histogram_tester.ExpectBucketCount(
+      "Autofill.StrikeDatabase.LocalCardMigrationNotOfferedDueToMaxStrikes",
+      AutofillMetrics::SaveTypeMetric::SERVER, 1);
+}
+
+// When local card migration is attempted and some cards aren't selected,
+// 3 strikes should be added.
+TEST_F(LocalCardMigrationManagerTest,
+       MigrateCreditCard_StrikesAddedWhenSomeCardsNotSelected) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2,
+       features::kAutofillLocalCardMigrationUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  LocalCardMigrationStrikeDatabase local_card_migration_strike_database =
+      LocalCardMigrationStrikeDatabase(strike_database_);
+  // LocalCardMigrationStrikeDatabase should initially have no strikes.
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 0);
+
+  AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "1", "guid1");
+  AddLocalCreditCard(personal_data_, "Flo Master", "5454545454545454", "11",
+                     test::NextYear().c_str(), "1", "guid2");
+  autofill_client_.GetPrefs()->SetDouble(prefs::kAutofillBillingCustomerNumber,
+                                         12345);
+  local_card_migration_manager_->GetMigratableCreditCards();
+
+  // Only select one of the two cards.
+  autofill_client_.set_migration_card_selections(
+      std::vector<std::string>{"guid1"});
+  local_card_migration_manager_->AttemptToOfferLocalCardMigration(true);
+
+  EXPECT_EQ(local_card_migration_strike_database.GetStrikes(), 3);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/local_card_migration_strike_database.cc b/components/autofill/core/browser/local_card_migration_strike_database.cc
index 6125d44..5c204950 100644
--- a/components/autofill/core/browser/local_card_migration_strike_database.cc
+++ b/components/autofill/core/browser/local_card_migration_strike_database.cc
@@ -8,6 +8,13 @@
 
 namespace autofill {
 
+const int LocalCardMigrationStrikeDatabase::kStrikesToRemoveWhenLocalCardAdded =
+    2;
+const int LocalCardMigrationStrikeDatabase::kStrikesToAddWhenBubbleClosed = 2;
+const int LocalCardMigrationStrikeDatabase::kStrikesToAddWhenDialogClosed = 3;
+const int LocalCardMigrationStrikeDatabase::
+    kStrikesToAddWhenCardsDeselectedAtMigration = 3;
+
 LocalCardMigrationStrikeDatabase::LocalCardMigrationStrikeDatabase(
     StrikeDatabase* strike_database)
     : StrikeDatabaseIntegratorBase(strike_database) {
diff --git a/components/autofill/core/browser/local_card_migration_strike_database.h b/components/autofill/core/browser/local_card_migration_strike_database.h
index ab25114..1124374 100644
--- a/components/autofill/core/browser/local_card_migration_strike_database.h
+++ b/components/autofill/core/browser/local_card_migration_strike_database.h
@@ -18,6 +18,16 @@
   LocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
   ~LocalCardMigrationStrikeDatabase() override;
 
+  // Strikes to remove when user adds new local card.
+  static const int kStrikesToRemoveWhenLocalCardAdded;
+  // Strikes to add when  user closes LocalCardMigrationBubble.
+  static const int kStrikesToAddWhenBubbleClosed;
+  // Strikes to add when user closes LocalCardMigrationDialog.
+  static const int kStrikesToAddWhenDialogClosed;
+  // Number of strikes to add when user de-selected some local cards during
+  // migration.
+  static const int kStrikesToAddWhenCardsDeselectedAtMigration;
+
   std::string GetProjectPrefix() override;
   int GetMaxStrikesLimit() override;
   long long GetExpiryTimeMicros() override;
diff --git a/components/autofill/core/browser/strike_database.cc b/components/autofill/core/browser/strike_database.cc
index 4bcc900..46a41fb3 100644
--- a/components/autofill/core/browser/strike_database.cc
+++ b/components/autofill/core/browser/strike_database.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/strike_database.h"
 
+#include <algorithm>
 #include <string>
 #include <utility>
 #include <vector>
@@ -39,7 +40,7 @@
 
 StrikeDatabase::~StrikeDatabase() {}
 
-int StrikeDatabase::AddStrikes(int strikes_increase, const std::string key) {
+int StrikeDatabase::AddStrikes(int strikes_increase, const std::string& key) {
   DCHECK(strikes_increase > 0);
   int num_strikes =
       strike_map_cache_.count(key)  // Cache has entry for |key|.
@@ -49,25 +50,20 @@
   return num_strikes;
 }
 
-int StrikeDatabase::RemoveStrikes(int strikes_decrease, const std::string key) {
-  DCHECK(strikes_decrease > 0);
-  DCHECK(strike_map_cache_.count(key));
-  int num_strikes = strike_map_cache_[key].num_strikes() - strikes_decrease;
-  if (num_strikes < 1) {
-    ClearStrikes(key);
-    return 0;
-  }
+int StrikeDatabase::RemoveStrikes(int strikes_decrease,
+                                  const std::string& key) {
+  int num_strikes = GetStrikes(key);
+  num_strikes = std::max(0, num_strikes - strikes_decrease);
   SetStrikeData(key, num_strikes);
   return num_strikes;
 }
 
-int StrikeDatabase::GetStrikes(const std::string key) {
-  return strike_map_cache_.count(key)  // Cache contains entry for |key|.
-             ? strike_map_cache_[key].num_strikes()
-             : 0;
+int StrikeDatabase::GetStrikes(const std::string& key) {
+  auto iter = strike_map_cache_.find(key);
+  return (iter != strike_map_cache_.end()) ? iter->second.num_strikes() : 0;
 }
 
-void StrikeDatabase::ClearStrikes(const std::string key) {
+void StrikeDatabase::ClearStrikes(const std::string& key) {
   strike_map_cache_.erase(key);
   ClearAllProtoStrikesForKey(key, base::DoNothing());
 }
@@ -123,7 +119,11 @@
   strike_map_cache_.insert(entries->begin(), entries->end());
 }
 
-void StrikeDatabase::SetStrikeData(const std::string key, int num_strikes) {
+void StrikeDatabase::SetStrikeData(const std::string& key, int num_strikes) {
+  if (num_strikes == 0) {
+    ClearStrikes(key);
+    return;
+  }
   StrikeData data;
   data.set_num_strikes(num_strikes);
   data.set_last_update_timestamp(
@@ -132,7 +132,7 @@
   SetProtoStrikeData(key, data, base::DoNothing());
 }
 
-void StrikeDatabase::GetProtoStrikes(const std::string key,
+void StrikeDatabase::GetProtoStrikes(const std::string& key,
                                      const StrikesCallback& outer_callback) {
   if (!database_initialized_) {
     outer_callback.Run(false);
@@ -173,7 +173,7 @@
       /*keys_to_remove=*/std::move(keys_to_remove), outer_callback);
 }
 
-void StrikeDatabase::GetProtoStrikeData(const std::string key,
+void StrikeDatabase::GetProtoStrikeData(const std::string& key,
                                         const GetValueCallback& callback) {
   if (!database_initialized_) {
     callback.Run(false, nullptr);
diff --git a/components/autofill/core/browser/strike_database.h b/components/autofill/core/browser/strike_database.h
index 3359224..1352dcd 100644
--- a/components/autofill/core/browser/strike_database.h
+++ b/components/autofill/core/browser/strike_database.h
@@ -54,18 +54,18 @@
 
   // Increases in-memory cache by |strikes_increase| and updates underlying
   // ProtoDatabase.
-  int AddStrikes(int strikes_increase, const std::string key);
+  int AddStrikes(int strikes_increase, const std::string& key);
 
   // Removes |strikes_decrease| in-memory cache strikes, updates
   // last_update_timestamp, and updates underlying ProtoDatabase.
-  int RemoveStrikes(int strikes_decrease, const std::string key);
+  int RemoveStrikes(int strikes_decrease, const std::string& key);
 
   // Returns strike count from in-memory cache.
-  int GetStrikes(const std::string key);
+  int GetStrikes(const std::string& key);
 
   // Removes database entry for |key| from in-memory cache and underlying
   // ProtoDatabase.
-  void ClearStrikes(const std::string key);
+  void ClearStrikes(const std::string& key);
 
   // Removes all database entries from in-memory cache and underlying
   // ProtoDatabase for the whole project.
@@ -117,12 +117,12 @@
 
   // Updates the StrikeData for |key| in the cache and ProtoDatabase to have
   // |num_strikes|, and the current time as timestamp.
-  void SetStrikeData(const std::string key, int num_strikes);
+  void SetStrikeData(const std::string& key, int num_strikes);
 
   // Passes the number of strikes for |key| to |outer_callback|. In the case
   // that the database fails to retrieve the strike update or if no entry is
   // found for |key|, 0 is passed.
-  virtual void GetProtoStrikes(const std::string key,
+  virtual void GetProtoStrikes(const std::string& key,
                                const StrikesCallback& outer_callback);
 
   // Removes all database entries, which ensures there will be no saved strikes
@@ -137,7 +137,7 @@
       const ClearStrikesCallback& outer_callback);
 
   // Passes success status and StrikeData entry for |key| to |inner_callback|.
-  void GetProtoStrikeData(const std::string key,
+  void GetProtoStrikeData(const std::string& key,
                           const GetValueCallback& inner_callback);
 
   // Sets the entry for |key| to |strike_data|. Success status is passed to the
diff --git a/components/autofill/core/browser/strike_database_integrator_base.cc b/components/autofill/core/browser/strike_database_integrator_base.cc
index 9d97f53..e3f3fd48 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.cc
+++ b/components/autofill/core/browser/strike_database_integrator_base.cc
@@ -27,18 +27,18 @@
 StrikeDatabaseIntegratorBase::~StrikeDatabaseIntegratorBase() {}
 
 bool StrikeDatabaseIntegratorBase::IsMaxStrikesLimitReached(
-    const std::string id) {
+    const std::string& id) {
   CheckIdUniqueness(id);
   return GetStrikes(id) >= GetMaxStrikesLimit();
 }
 
-int StrikeDatabaseIntegratorBase::AddStrike(const std::string id) {
+int StrikeDatabaseIntegratorBase::AddStrike(const std::string& id) {
   CheckIdUniqueness(id);
   return AddStrikes(1, id);
 }
 
 int StrikeDatabaseIntegratorBase::AddStrikes(int strikes_increase,
-                                             const std::string id) {
+                                             const std::string& id) {
   CheckIdUniqueness(id);
   int num_strikes = strike_database_->AddStrikes(strikes_increase, GetKey(id));
   base::UmaHistogramCounts1000(
@@ -47,23 +47,23 @@
   return num_strikes;
 }
 
-int StrikeDatabaseIntegratorBase::RemoveStrike(const std::string id) {
+int StrikeDatabaseIntegratorBase::RemoveStrike(const std::string& id) {
   CheckIdUniqueness(id);
   return strike_database_->RemoveStrikes(1, GetKey(id));
 }
 
-int StrikeDatabaseIntegratorBase::RemoveStrikes(int strikes_decrease,
-                                                const std::string id) {
+int StrikeDatabaseIntegratorBase::RemoveStrikes(int strike_decrease,
+                                                const std::string& id) {
   CheckIdUniqueness(id);
-  return strike_database_->RemoveStrikes(strikes_decrease, GetKey(id));
+  return strike_database_->RemoveStrikes(strike_decrease, GetKey(id));
 }
 
-int StrikeDatabaseIntegratorBase::GetStrikes(const std::string id) {
+int StrikeDatabaseIntegratorBase::GetStrikes(const std::string& id) {
   CheckIdUniqueness(id);
   return strike_database_->GetStrikes(GetKey(id));
 }
 
-void StrikeDatabaseIntegratorBase::ClearStrikes(const std::string id) {
+void StrikeDatabaseIntegratorBase::ClearStrikes(const std::string& id) {
   CheckIdUniqueness(id);
   strike_database_->ClearStrikes(GetKey(id));
 }
@@ -93,7 +93,7 @@
   }
 }
 
-std::string StrikeDatabaseIntegratorBase::GetKey(const std::string id) {
+std::string StrikeDatabaseIntegratorBase::GetKey(const std::string& id) {
   return GetProjectPrefix() + kKeyDeliminator + id;
 }
 
diff --git a/components/autofill/core/browser/strike_database_integrator_base.h b/components/autofill/core/browser/strike_database_integrator_base.h
index 81a48f91..75229c5 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.h
+++ b/components/autofill/core/browser/strike_database_integrator_base.h
@@ -24,29 +24,29 @@
 
   // Returns whether or not strike count for |id| has reached the strike limit
   // set by GetMaxStrikesLimit().
-  bool IsMaxStrikesLimitReached(const std::string id = kSharedId);
+  bool IsMaxStrikesLimitReached(const std::string& id = kSharedId);
 
   // Increments in-memory cache and updates underlying ProtoDatabase.
-  int AddStrike(const std::string id = kSharedId);
+  int AddStrike(const std::string& id = kSharedId);
 
   // Increases in-memory cache by |strikes_increase| and updates underlying
   // ProtoDatabase.
-  int AddStrikes(int strikes_increase, const std::string id = kSharedId);
+  int AddStrikes(int strikes_increase, const std::string& id = kSharedId);
 
   // Removes an in-memory cache strike, updates last_update_timestamp, and
   // updates underlying ProtoDatabase.
-  int RemoveStrike(const std::string id = kSharedId);
+  int RemoveStrike(const std::string& id = kSharedId);
 
   // Removes |strikes_decrease| in-memory cache strikes, updates
   // |last_update_timestamp|, and updates underlying ProtoDatabase.
-  int RemoveStrikes(int strikes_decrease, const std::string id = kSharedId);
+  int RemoveStrikes(int strikes_decrease, const std::string& id = kSharedId);
 
   // Returns strike count from in-memory cache.
-  int GetStrikes(const std::string id = kSharedId);
+  int GetStrikes(const std::string& id = kSharedId);
 
   // Removes all database entries from in-memory cache and underlying
   // ProtoDatabase.
-  void ClearStrikes(const std::string id = kSharedId);
+  void ClearStrikes(const std::string& id = kSharedId);
 
   // Removes all database entries from in-memory cache and underlying
   // ProtoDatabase for the whole project.
@@ -82,7 +82,7 @@
   }
 
   // Generates key based on project-specific string identifier.
-  std::string GetKey(const std::string id);
+  std::string GetKey(const std::string& id);
 
   // Returns a prefix unique to each project, which will be used to create
   // database key.
diff --git a/components/autofill/core/browser/test_strike_database.cc b/components/autofill/core/browser/test_strike_database.cc
index bb242f4..4aee9cb 100644
--- a/components/autofill/core/browser/test_strike_database.cc
+++ b/components/autofill/core/browser/test_strike_database.cc
@@ -13,7 +13,7 @@
 TestStrikeDatabase::~TestStrikeDatabase() {}
 
 void TestStrikeDatabase::GetProtoStrikes(
-    const std::string key,
+    const std::string& key,
     const StrikesCallback& outer_callback) {
   outer_callback.Run(GetStrikesForTesting(key));
 }
diff --git a/components/autofill/core/browser/test_strike_database.h b/components/autofill/core/browser/test_strike_database.h
index ebe7731..d46be27b 100644
--- a/components/autofill/core/browser/test_strike_database.h
+++ b/components/autofill/core/browser/test_strike_database.h
@@ -22,7 +22,7 @@
   ~TestStrikeDatabase() override;
 
   // StrikeDatabase:
-  void GetProtoStrikes(const std::string key,
+  void GetProtoStrikes(const std::string& key,
                        const StrikesCallback& outer_callback) override;
   void ClearAllProtoStrikes(
       const ClearStrikesCallback& outer_callback) override;
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 5fdf760..1715e1d 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -147,6 +147,12 @@
     "AutofillLocalCardMigrationShowFeedback",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Controls whether offering to migrate cards will consider data from the
+// Autofill strike database (new version).
+const base::Feature kAutofillLocalCardMigrationUsesStrikeSystemV2{
+    "AutofillLocalCardMigrationUsesStrikeSystemV2",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether the manual fallback will be present.
 const base::Feature kAutofillManualFallback{"AutofillManualFallback",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index ec9c4784..e23fa2c0 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -46,12 +46,13 @@
 extern const base::Feature kAutofillImportNonFocusableCreditCardForms;
 extern const base::Feature kAutofillKeyboardAccessory;
 extern const base::Feature kAutofillLocalCardMigrationShowFeedback;
+extern const base::Feature kAutofillLocalCardMigrationUsesStrikeSystemV2;
 extern const base::Feature kAutofillManualFallback;
 extern const base::Feature kAutofillManualFallbackPhaseTwo;
 extern const base::Feature kAutofillMetadataUploads;
-extern const base::Feature kAutofillPreferServerNamePredictions;
 extern const base::Feature kAutofillNoLocalSaveOnUploadSuccess;
 extern const base::Feature kAutofillOverrideWithRaterConsensus;
+extern const base::Feature kAutofillPreferServerNamePredictions;
 extern const base::Feature kAutofillPrefilledFields;
 extern const base::Feature kAutofillProfileServerValidation;
 extern const base::Feature kAutofillRestrictUnownedFieldsToFormlessCheckout;
diff --git a/components/download/internal/common/android/download_collection_bridge.cc b/components/download/internal/common/android/download_collection_bridge.cc
index 113dc49..af7d653b 100644
--- a/components/download/internal/common/android/download_collection_bridge.cc
+++ b/components/download/internal/common/android/download_collection_bridge.cc
@@ -115,4 +115,18 @@
       ConvertUTF8ToJavaString(env, file_name.value());
   return Java_DownloadCollectionBridge_fileNameExists(env, jfile_name);
 }
+
+// static
+bool DownloadCollectionBridge::renameDownloadUri(
+    const base::FilePath& download_uri,
+    const base::FilePath& new_display_name) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> jdownload_uri =
+      ConvertUTF8ToJavaString(env, download_uri.value());
+  ScopedJavaLocalRef<jstring> jdisplay_name =
+      ConvertUTF8ToJavaString(env, new_display_name.value());
+  return Java_DownloadCollectionBridge_renameDownloadUri(env, jdownload_uri,
+                                                         jdisplay_name);
+}
+
 }  // namespace download
diff --git a/components/download/internal/common/android/download_collection_bridge.h b/components/download/internal/common/android/download_collection_bridge.h
index 2cfdfdef..dc39ddc 100644
--- a/components/download/internal/common/android/download_collection_bridge.h
+++ b/components/download/internal/common/android/download_collection_bridge.h
@@ -46,6 +46,12 @@
   // Checks whether a file name exists.
   static bool FileNameExists(const base::FilePath& file_name);
 
+  // Renames a content URI download to |new_display_name|. Returns true on
+  // success, and false otherwise.
+  // Called on non UI thread.
+  static bool renameDownloadUri(const base::FilePath& download_uri,
+                                const base::FilePath& new_display_name);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(DownloadCollectionBridge);
 };
diff --git a/components/download/internal/common/android/java/src/org/chromium/components/download/DownloadCollectionBridge.java b/components/download/internal/common/android/java/src/org/chromium/components/download/DownloadCollectionBridge.java
index 0df8624..e667b9d 100644
--- a/components/download/internal/common/android/java/src/org/chromium/components/download/DownloadCollectionBridge.java
+++ b/components/download/internal/common/android/java/src/org/chromium/components/download/DownloadCollectionBridge.java
@@ -100,6 +100,16 @@
     }
 
     /**
+     * Renames a download Uri with a display name.
+     * @param downloadUri Uri of the download.
+     * @param displayName New display name for the download.
+     * @return whether rename was successful.
+     */
+    protected boolean rename(final String downloadUri, final String displayName) {
+        return false;
+    }
+
+    /**
      * Creates an intermediate URI for download to be written into. On completion, call
      * nativeOnCreateIntermediateUriResult() with |callbackId|.
      * @param fileName Name of the file.
@@ -182,4 +192,15 @@
     private static boolean fileNameExists(final String fileName) {
         return getDownloadCollectionBridge().checkFileNameExists(fileName);
     }
+
+    /**
+     * Renames a download Uri with a display name.
+     * @param downloadUri Uri of the download.
+     * @param displayName New display name for the download.
+     * @return whether rename was successful.
+     */
+    @CalledByNative
+    private static boolean renameDownloadUri(final String downloadUri, final String displayName) {
+        return getDownloadCollectionBridge().rename(downloadUri, displayName);
+    }
 }
diff --git a/components/drive/event_logger.h b/components/drive/event_logger.h
index bcb0f05..e877d615 100644
--- a/components/drive/event_logger.h
+++ b/components/drive/event_logger.h
@@ -46,9 +46,8 @@
 
   // Logs a message with formatting.
   // Can be called from any thread as long as the object is alive.
-  void Log(logging::LogSeverity severity,
-           _Printf_format_string_ const char* format,
-           ...) PRINTF_FORMAT(3, 4);
+  void Log(logging::LogSeverity severity, const char* format, ...)
+      PRINTF_FORMAT(3, 4);
 
   // Sets the history size. The existing history is cleared.
   // Can be called from any thread as long as the object is alive.
diff --git a/components/metrics/call_stack_profile_builder.cc b/components/metrics/call_stack_profile_builder.cc
index 9240d3b..cde0fb13 100644
--- a/components/metrics/call_stack_profile_builder.cc
+++ b/components/metrics/call_stack_profile_builder.cc
@@ -168,7 +168,8 @@
     CallStackProfile::ModuleIdentifier* module_id =
         call_stack_profile->add_module_id();
     module_id->set_build_id(module->GetId());
-    module_id->set_name_md5_prefix(HashModuleFilename(module->GetFilename()));
+    module_id->set_name_md5_prefix(
+        HashModuleFilename(module->GetDebugBasename()));
   }
 
   PassProfilesToMetricsProvider(std::move(sampled_profile_));
diff --git a/components/metrics/call_stack_profile_builder_unittest.cc b/components/metrics/call_stack_profile_builder_unittest.cc
index f956b9dd..9922b4af 100644
--- a/components/metrics/call_stack_profile_builder_unittest.cc
+++ b/components/metrics/call_stack_profile_builder_unittest.cc
@@ -85,15 +85,15 @@
 #endif
 
   const uintptr_t module_base_address1 = 0x1000;
-  Module module1(module_base_address1, "1", module_path);
+  Module module1(module_base_address1, "1", module_path, 0x100);
   Frame frame1 = {module_base_address1 + 0x10, &module1};
 
   const uintptr_t module_base_address2 = 0x1100;
-  Module module2(module_base_address2, "2", module_path);
+  Module module2(module_base_address2, "2", module_path, 0x10);
   Frame frame2 = {module_base_address2 + 0x10, &module2};
 
   const uintptr_t module_base_address3 = 0x1010;
-  Module module3(module_base_address3, "3", module_path);
+  Module module3(module_base_address3, "3", module_path, 0x100);
   Frame frame3 = {module_base_address3 + 0x10, &module3};
 
   std::vector<Frame> frames1 = {frame1, frame2};
@@ -165,11 +165,11 @@
 #endif
 
   const uintptr_t module_base_address1 = 0x1000;
-  Module module1(module_base_address1, "1", module_path);
+  Module module1(module_base_address1, "1", module_path, 0x100);
   Frame frame1 = {module_base_address1 + 0x10, &module1};
 
   const uintptr_t module_base_address2 = 0x1100;
-  Module module2(module_base_address2, "2", module_path);
+  Module module2(module_base_address2, "2", module_path, 0x100);
   Frame frame2 = {module_base_address2 + 0x10, &module2};
 
   std::vector<Frame> frames = {frame1, frame2};
@@ -211,11 +211,11 @@
 #endif
 
   const uintptr_t module_base_address1 = 0x1000;
-  Module module1(module_base_address1, "1", module_path);
+  Module module1(module_base_address1, "1", module_path, 0x100);
   Frame frame1 = {module_base_address1 + 0x10, &module1};
 
   const uintptr_t module_base_address2 = 0x1100;
-  Module module2(module_base_address2, "2", module_path);
+  Module module2(module_base_address2, "2", module_path, 0x100);
   Frame frame2 = {module_base_address2 + 0x10, &module2};
 
   std::vector<Frame> frames1 = {frame1};
@@ -262,7 +262,7 @@
   uint64_t module_md5 = 0x554838A8451AC36CULL;
   base::FilePath module_path("/some/path/to/chrome");
 #endif
-  Module module2(module_base_address2, "2", module_path);
+  Module module2(module_base_address2, "2", module_path, 0x100);
   Frame frame2 = {module_base_address2 + 0x10, &module2};
 
   std::vector<Frame> frames = {frame1, frame2};
@@ -311,7 +311,7 @@
   base::FilePath module_path("/some/path/to/chrome");
 #endif
 
-  Module module(module_base_address, "1", module_path);
+  Module module(module_base_address, "1", module_path, 0x100);
   Frame frame1 = {module_base_address + 0x10, &module};
   Frame frame2 = {module_base_address + 0x20, &module};
 
@@ -369,7 +369,7 @@
   base::FilePath module_path("/some/path/to/chrome");
 #endif
 
-  Module module(0x1000, "1", module_path);
+  Module module(0x1000, "1", module_path, 0x100);
   Frame frame = {0x1000 + 0x10, &module};
 
   // Id 0 means the message loop hasn't been started yet, so the sample should
@@ -426,7 +426,7 @@
   base::FilePath module_path("/some/path/to/chrome");
 #endif
 
-  Module module = {0x1000, "1", module_path};
+  Module module = {0x1000, "1", module_path, 0x100};
   Frame frame = {0x1000 + 0x10, &module};
 
   metadata_recorder.current_value = 5;
diff --git a/components/policy_strings.grdp b/components/policy_strings.grdp
index be8d439..83926d7 100644
--- a/components/policy_strings.grdp
+++ b/components/policy_strings.grdp
@@ -322,6 +322,12 @@
   <message name="IDS_POLICY_LABEL_MESSAGES" desc="Label for the messages row in the policy table.">
     Messages
   </message>
+  <message name="IDS_POLICY_LABEL_CONFLICT" desc="Label for the conflict row in the policy table.">
+    Conflict
+  </message>
+  <message name="IDS_POLICY_LABEL_WARNING_AND_CONFLICT" desc="Label for the conflict row in the policy table when there are warnings and conflicts.">
+    Warnings, Conflict
+  </message>
   <message name="IDS_POLICY_LABEL_VALUE" desc="Label for the value row in the policy table.">
     Value
   </message>
@@ -355,11 +361,11 @@
   <message name="IDS_POLICY_HEADER_WARNING" desc="Table header for the column in the policy table that contains the policy warnings.">
     Warning
   </message>
-  <message name="IDS_POLICY_SHOW_EXPANDED_VALUE" desc="Text for the link that shows the policy value. Used when the policy value is too long to be always visible.">
-    Show value
+  <message name="IDS_POLICY_SHOW_MORE" desc="Text for the link that expands and shows the are full value, conflicts and warnings rows of a policy.">
+    Show more
   </message>
-  <message name="IDS_POLICY_HIDE_EXPANDED_VALUE" desc="Text for the link that hides the policy value. Used when the policy value is too long to be always visible.">
-    Hide value
+  <message name="IDS_POLICY_SHOW_LESS" desc="Text for the link that collapses and hides the are full value, conflicts and warnings rows of a policy.">
+    Show less
   </message>
   <message name="IDS_POLICY_LEARN_MORE" desc="Help text for learn-more link for known chrome policies.">
     Learn more about <ph name="POLICY_NAME">$1<ex>AllowDinosaurEasterEgg</ex></ph> policy
diff --git a/components/previews/content/previews_decider_impl_unittest.cc b/components/previews/content/previews_decider_impl_unittest.cc
index afd5115e..42ec7bc 100644
--- a/components/previews/content/previews_decider_impl_unittest.cc
+++ b/components/previews/content/previews_decider_impl_unittest.cc
@@ -823,7 +823,7 @@
       &user_data, GURL("file:///sdcard"), false, PreviewsType::LOFI));
 }
 
-TEST_F(PreviewsDeciderImplTest, ClientLoFiAllowedOnReload) {
+TEST_F(PreviewsDeciderImplTest, ClientLoFiDisallowedOnReload) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
       {features::kPreviews, features::kClientLoFi}, {});
@@ -836,11 +836,11 @@
   PreviewsUserData user_data(kDefaultPageId);
 
   base::HistogramTester histogram_tester;
-  EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
+  EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
       &user_data, GURL("https://www.google.com"), true, PreviewsType::LOFI));
   histogram_tester.ExpectUniqueSample(
       "Previews.EligibilityReason.LoFi",
-      static_cast<int>(PreviewsEligibilityReason::ALLOWED), 1);
+      static_cast<int>(PreviewsEligibilityReason::RELOAD_DISALLOWED), 1);
 }
 
 TEST_F(PreviewsDeciderImplTest, NoScriptFeatureDefaultBehavior) {
diff --git a/components/previews/core/previews_features.cc b/components/previews/core/previews_features.cc
index 7bd129d..56a744c 100644
--- a/components/previews/core/previews_features.cc
+++ b/components/previews/core/previews_features.cc
@@ -98,7 +98,7 @@
 
 // A feature to prevent previews on all reloads.
 const base::Feature kPreviewsDisallowedOnReloads{
-    "PreviewsDisallowedOnReloads", base::FEATURE_DISABLED_BY_DEFAULT};
+    "PreviewsDisallowedOnReloads", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Allows HTTPS previews to be served via a URLLoader when network service is
 // enabled.
diff --git a/components/safe_browsing/android/remote_database_manager.cc b/components/safe_browsing/android/remote_database_manager.cc
index efd76d2..23addbe 100644
--- a/components/safe_browsing/android/remote_database_manager.cc
+++ b/components/safe_browsing/android/remote_database_manager.cc
@@ -119,7 +119,7 @@
   if (ints_str.empty()) {
     // By default, we check all types except a few.
     static_assert(content::RESOURCE_TYPE_LAST_TYPE ==
-                      content::RESOURCE_TYPE_PLUGIN_RESOURCE + 1,
+                      content::RESOURCE_TYPE_NAVIGATION_PRELOAD + 1,
                   "Decide if new resource type should be skipped on mobile.");
     for (int t_int = 0; t_int < content::RESOURCE_TYPE_LAST_TYPE; t_int++) {
       content::ResourceType t = static_cast<content::ResourceType>(t_int);
diff --git a/components/safe_browsing/db/BUILD.gn b/components/safe_browsing/db/BUILD.gn
index 12485fb..a710fe5 100644
--- a/components/safe_browsing/db/BUILD.gn
+++ b/components/safe_browsing/db/BUILD.gn
@@ -2,8 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//third_party/protobuf/proto_library.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
+import("//third_party/protobuf/proto_library.gni")
 
 proto_library("safebrowsing_proto") {
   sources = [
@@ -482,3 +482,14 @@
     ":v4_get_hash_protocol_manager",
   ]
 }
+
+fuzzer_test("v4_store_fuzzer") {
+  sources = [
+    "v4_store_fuzzer.cc",
+  ]
+  deps = [
+    ":v4_store",
+    ":v4_test_util",
+    "//base/test:test_support",
+  ]
+}
diff --git a/components/safe_browsing/db/v4_store.h b/components/safe_browsing/db/v4_store.h
index 3812af0..466e8afc 100644
--- a/components/safe_browsing/db/v4_store.h
+++ b/components/safe_browsing/db/v4_store.h
@@ -296,6 +296,7 @@
   FRIEND_TEST_ALL_PREFIXES(V4StorePerftest, StressTest);
 
   friend class V4StoreTest;
+  friend class V4StoreFuzzer;
 
   // If |prefix_size| is within expected range, and |raw_hashes_length| is a
   // multiple of prefix_size, then it sets the string of length
diff --git a/components/safe_browsing/db/v4_store_fuzzer.cc b/components/safe_browsing/db/v4_store_fuzzer.cc
new file mode 100644
index 0000000..4ddea7a
--- /dev/null
+++ b/components/safe_browsing/db/v4_store_fuzzer.cc
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/test/test_simple_task_runner.h"
+#include "components/safe_browsing/db/v4_store.h"
+#include "components/safe_browsing/db/v4_test_util.h"
+
+namespace safe_browsing {
+
+class V4StoreFuzzer {
+ public:
+  static int FuzzMergeUpdate(const uint8_t* data, size_t size) {
+    // Empty update, not interesting.
+    if (size == 0)
+      return 0;
+
+    size_t num_prefixes_first_half = size / (2 * kMinHashPrefixLength);
+    size_t first_half_size = num_prefixes_first_half * kMinHashPrefixLength;
+
+    std::string first_half(data, data + first_half_size);
+    HashPrefixMap prefix_map_old;
+    V4Store::AddUnlumpedHashes(kMinHashPrefixLength, first_half,
+                               &prefix_map_old);
+
+    std::string second_half(data + first_half_size, data + size);
+    HashPrefixMap prefix_map_additions;
+    V4Store::AddUnlumpedHashes(kMinHashPrefixLength, second_half,
+                               &prefix_map_additions);
+
+    auto store = std::make_unique<TestV4Store>(
+        base::MakeRefCounted<base::TestSimpleTaskRunner>(), base::FilePath());
+    google::protobuf::RepeatedField<google::protobuf::int32> raw_removals;
+    std::string empty_checksum;
+    store->MergeUpdate(prefix_map_old, prefix_map_additions, &raw_removals,
+                       empty_checksum);
+
+    return 0;
+  }
+};
+
+}  // namespace safe_browsing
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  return safe_browsing::V4StoreFuzzer::FuzzMergeUpdate(data, size);
+}
diff --git a/components/send_tab_to_self/proto/send_tab_to_self.proto b/components/send_tab_to_self/proto/send_tab_to_self.proto
index 3294d38e..61d7504 100644
--- a/components/send_tab_to_self/proto/send_tab_to_self.proto
+++ b/components/send_tab_to_self/proto/send_tab_to_self.proto
@@ -17,4 +17,7 @@
 message SendTabToSelfLocal {
   // The Send tab to self specifics proto.
   optional sync_pb.SendTabToSelfSpecifics specifics = 1;
+
+  // Has the notification for this proto been dismissed.
+  optional bool notification_dismissed = 2;
 }
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.cc b/components/send_tab_to_self/send_tab_to_self_bridge.cc
index e05aaf1..584ceda 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.cc
@@ -252,13 +252,18 @@
 }
 
 void SendTabToSelfBridge::DismissEntry(const std::string& guid) {
+  SendTabToSelfEntry* entry = GetMutableEntryByGUID(guid);
   // Assure that an entry with that guid exists.
-  if (GetEntryByGUID(guid) == nullptr) {
+  if (!entry) {
     return;
   }
+  entry->SetNotificationDismissed(true);
 
-  NOTIMPLEMENTED();
-  // TODO(jeffreycohen) Implement once there is local storage.
+  std::unique_ptr<ModelTypeStore::WriteBatch> batch =
+      store_->CreateWriteBatch();
+
+  batch->WriteData(guid, entry->AsLocalProto().SerializeAsString());
+  Commit(std::move(batch));
 }
 
 // static
@@ -367,4 +372,13 @@
                                           weak_ptr_factory_.GetWeakPtr()));
 }
 
+SendTabToSelfEntry* SendTabToSelfBridge::GetMutableEntryByGUID(
+    const std::string& guid) const {
+  auto it = entries_.find(guid);
+  if (it == entries_.end()) {
+    return nullptr;
+  }
+  return it->second.get();
+}
+
 }  // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.h b/components/send_tab_to_self/send_tab_to_self_bridge.h
index feb5a4e6..9f01c34c 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.h
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.h
@@ -108,6 +108,10 @@
   // Persists the changes in the given aggregators
   void Commit(std::unique_ptr<syncer::ModelTypeStore::WriteBatch> batch);
 
+  // Returns a specific entry for editing. Returns null if the entry does not
+  // exist.
+  SendTabToSelfEntry* GetMutableEntryByGUID(const std::string& guid) const;
+
   // |entries_| is keyed by GUIDs.
   SendTabToSelfEntries entries_;
 
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
index be7650e1..1321319ec 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
@@ -284,7 +284,7 @@
   InitializeBridge();
 
   std::vector<std::string> guids = bridge()->GetAllGuids();
-  EXPECT_EQ(1ul, guids.size());
+  ASSERT_EQ(1ul, guids.size());
   EXPECT_EQ(specifics.url(),
             bridge()->GetEntryByGUID(guids[0])->GetURL().spec());
 }
@@ -329,6 +329,31 @@
   EXPECT_FALSE(error);
 }
 
+TEST_F(SendTabToSelfBridgeTest, PreserveDissmissalAfterRestartBridge) {
+  InitializeBridge();
+
+  const sync_pb::SendTabToSelfSpecifics specifics = CreateSpecifics(1);
+  std::unique_ptr<syncer::MetadataChangeList> metadata_changes =
+      bridge()->CreateMetadataChangeList();
+
+  auto error = bridge()->ApplySyncChanges(std::move(metadata_changes),
+                                          EntityAddList({specifics}));
+  ASSERT_FALSE(error);
+
+  EXPECT_CALL(*processor(), Put(_, _, _)).Times(0);
+  EXPECT_CALL(*processor(), Delete(_, _)).Times(0);
+
+  bridge()->DismissEntry(specifics.guid());
+
+  ShutdownBridge();
+
+  InitializeBridge();
+
+  std::vector<std::string> guids = bridge()->GetAllGuids();
+  ASSERT_EQ(1ul, guids.size());
+  EXPECT_TRUE(bridge()->GetEntryByGUID(guids[0])->GetNotificationDismissed());
+}
+
 }  // namespace
 
 }  // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_entry.cc b/components/send_tab_to_self/send_tab_to_self_entry.cc
index 9e6c964..f8e7eac 100644
--- a/components/send_tab_to_self/send_tab_to_self_entry.cc
+++ b/components/send_tab_to_self/send_tab_to_self_entry.cc
@@ -4,6 +4,8 @@
 
 #include "components/send_tab_to_self/send_tab_to_self_entry.h"
 
+#include <memory>
+
 #include "base/guid.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -39,7 +41,8 @@
       title_(title),
       device_name_(device_name),
       shared_time_(shared_time),
-      original_navigation_time_(original_navigation_time) {
+      original_navigation_time_(original_navigation_time),
+      notification_dismissed_(false) {
   DCHECK(!guid_.empty());
   DCHECK(url_.is_valid());
 }
@@ -70,6 +73,14 @@
   return device_name_;
 }
 
+void SendTabToSelfEntry::SetNotificationDismissed(bool notification_dismissed) {
+  notification_dismissed_ = notification_dismissed;
+}
+
+bool SendTabToSelfEntry::GetNotificationDismissed() const {
+  return notification_dismissed_;
+}
+
 SendTabToSelfLocal SendTabToSelfEntry::AsLocalProto() const {
   SendTabToSelfLocal local_entry;
   auto* pb_entry = local_entry.mutable_specifics();
@@ -82,6 +93,8 @@
       TimeToProtoTime(GetOriginalNavigationTime()));
   pb_entry->set_device_name(GetDeviceName());
 
+  local_entry.set_notification_dismissed(GetNotificationDismissed());
+
   return local_entry;
 }
 
@@ -112,7 +125,10 @@
 std::unique_ptr<SendTabToSelfEntry> SendTabToSelfEntry::FromLocalProto(
     const SendTabToSelfLocal& local_entry,
     base::Time now) {
-  return FromProto(local_entry.specifics(), now);
+  std::unique_ptr<SendTabToSelfEntry> to_return =
+      FromProto(local_entry.specifics(), now);
+  to_return->SetNotificationDismissed(local_entry.notification_dismissed());
+  return to_return;
 }
 
 }  // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_entry.h b/components/send_tab_to_self/send_tab_to_self_entry.h
index 06d36495..6b8011d 100644
--- a/components/send_tab_to_self/send_tab_to_self_entry.h
+++ b/components/send_tab_to_self/send_tab_to_self_entry.h
@@ -51,6 +51,10 @@
   // The name of the device that originated the sent tab.
   const std::string& GetDeviceName() const;
 
+  // The state of this entry's notification: if it has been |dismissed|.
+  void SetNotificationDismissed(bool notification_dismissed);
+  bool GetNotificationDismissed() const;
+
   // Returns a protobuf encoding the content of this SendTabToSelfEntry for
   // local storage.
   SendTabToSelfLocal AsLocalProto() const;
@@ -74,6 +78,7 @@
   std::string device_name_;
   base::Time shared_time_;
   base::Time original_navigation_time_;
+  bool notification_dismissed_;
 
   DISALLOW_COPY_AND_ASSIGN(SendTabToSelfEntry);
 };
diff --git a/components/services/font/public/cpp/font_loader.cc b/components/services/font/public/cpp/font_loader.cc
index e35c619..d8076b0e 100644
--- a/components/services/font/public/cpp/font_loader.cc
+++ b/components/services/font/public/cpp/font_loader.cc
@@ -22,11 +22,6 @@
 
 FontLoader::~FontLoader() {}
 
-void FontLoader::Shutdown() {
-  thread_->Stop();
-  thread_ = nullptr;
-}
-
 bool FontLoader::matchFamilyName(const char family_name[],
                                  SkFontStyle requested,
                                  FontIdentity* out_font_identifier,
diff --git a/components/services/font/public/cpp/font_loader.h b/components/services/font/public/cpp/font_loader.h
index 602d5f9..96fc2b2 100644
--- a/components/services/font/public/cpp/font_loader.h
+++ b/components/services/font/public/cpp/font_loader.h
@@ -37,9 +37,6 @@
   explicit FontLoader(service_manager::Connector* connector);
   ~FontLoader() override;
 
-  // Shuts down the background thread.
-  void Shutdown();
-
   // SkFontConfigInterface:
   bool matchFamilyName(const char family_name[],
                        SkFontStyle requested,
diff --git a/components/services/font/public/cpp/font_service_thread.cc b/components/services/font/public/cpp/font_service_thread.cc
index d18ed96..5e39791 100644
--- a/components/services/font/public/cpp/font_service_thread.cc
+++ b/components/services/font/public/cpp/font_service_thread.cc
@@ -9,36 +9,35 @@
 #include "base/bind.h"
 #include "base/files/file.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/task/post_task.h"
 #include "components/services/font/public/cpp/mapped_font_file.h"
 
 namespace font_service {
 namespace internal {
 
-namespace {
-const char kFontThreadName[] = "Font_Proxy_Thread";
-}  // namespace
-
 FontServiceThread::FontServiceThread(mojom::FontServicePtr font_service)
-    : base::Thread(kFontThreadName),
-      font_service_info_(font_service.PassInterface()),
+    : font_service_info_(font_service.PassInterface()),
+      task_runner_(base::CreateSequencedTaskRunnerWithTraits(
+          {base::TaskPriority::USER_VISIBLE, base::MayBlock()})),
       weak_factory_(this) {
-  DETACH_FROM_THREAD(thread_checker_);
-  Start();
+  task_runner_->PostTask(FROM_HERE, base::BindOnce(&FontServiceThread::Init,
+                                                   weak_factory_.GetWeakPtr()));
 }
 
+FontServiceThread::~FontServiceThread() {}
+
 bool FontServiceThread::MatchFamilyName(
     const char family_name[],
     SkFontStyle requested_style,
     SkFontConfigInterface::FontIdentity* out_font_identity,
     SkString* out_family_name,
     SkFontStyle* out_style) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
-
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
   bool out_valid = false;
   // This proxies to the other thread, which proxies to mojo. Only on the reply
   // from mojo do we return from this.
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&FontServiceThread::MatchFamilyNameImpl, this, &done_event,
                      family_name, requested_style, &out_valid,
@@ -55,10 +54,10 @@
     std::string* out_family_name,
     bool* out_is_bold,
     bool* out_is_italic) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
   bool out_valid = false;
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&FontServiceThread::FallbackFontForCharacterImpl, this,
                      &done_event, character, std::move(locale), &out_valid,
@@ -76,10 +75,10 @@
     bool is_bold,
     float device_scale_factor,
     font_service::mojom::FontRenderStylePtr* out_font_render_style) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
   bool out_valid = false;
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&FontServiceThread::FontRenderStyleForStrikeImpl, this,
                      &done_event, family, size, is_italic, is_bold,
@@ -91,10 +90,10 @@
 bool FontServiceThread::MatchFontByPostscriptNameOrFullFontName(
     std::string postscript_name_or_full_font_name,
     mojom::FontIdentityPtr* out_identity) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
   bool out_valid = false;
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
           &FontServiceThread::MatchFontByPostscriptNameOrFullFontNameImpl, this,
@@ -111,9 +110,9 @@
     uint32_t charset,
     uint32_t fallback_family_type,
     base::File* out_font_file_handle) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&FontServiceThread::MatchFontWithFallbackImpl, this,
                      &done_event, std::move(family), is_bold, is_italic,
@@ -123,13 +122,13 @@
 
 scoped_refptr<MappedFontFile> FontServiceThread::OpenStream(
     const SkFontConfigInterface::FontIdentity& identity) {
-  DCHECK_NE(GetThreadId(), base::PlatformThread::CurrentId());
+  DCHECK(!task_runner_->RunsTasksInCurrentSequence());
 
   base::File stream_file;
   // This proxies to the other thread, which proxies to mojo. Only on the
   // reply from mojo do we return from this.
   base::WaitableEvent done_event;
-  task_runner()->PostTask(
+  task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&FontServiceThread::OpenStreamImpl, this,
                                 &done_event, &stream_file, identity.fID));
   done_event.Wait();
@@ -148,10 +147,6 @@
   return mapped_font_file;
 }
 
-FontServiceThread::~FontServiceThread() {
-  Stop();
-}
-
 void FontServiceThread::MatchFamilyNameImpl(
     base::WaitableEvent* done_event,
     const char family_name[],
@@ -160,7 +155,7 @@
     SkFontConfigInterface::FontIdentity* out_font_identity,
     SkString* out_family_name,
     SkFontStyle* out_style) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   if (font_service_.encountered_error()) {
     *out_valid = false;
@@ -192,7 +187,8 @@
     mojom::FontIdentityPtr font_identity,
     const std::string& family_name,
     mojom::TypefaceStylePtr style) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
 
   *out_valid = !font_identity.is_null();
@@ -214,7 +210,8 @@
 void FontServiceThread::OpenStreamImpl(base::WaitableEvent* done_event,
                                        base::File* output_file,
                                        const uint32_t id_number) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   if (font_service_.encountered_error()) {
     done_event->Signal();
     return;
@@ -229,7 +226,8 @@
 void FontServiceThread::OnOpenStreamComplete(base::WaitableEvent* done_event,
                                              base::File* output_file,
                                              base::File file) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
   *output_file = std::move(file);
   done_event->Signal();
@@ -244,7 +242,7 @@
     std::string* out_family_name,
     bool* out_is_bold,
     bool* out_is_italic) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   if (font_service_.encountered_error()) {
     *out_valid = false;
@@ -271,7 +269,8 @@
     const std::string& family_name,
     bool is_bold,
     bool is_italic) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
 
   *out_valid = !font_identity.is_null();
@@ -293,7 +292,7 @@
     float device_scale_factor,
     bool* out_valid,
     mojom::FontRenderStylePtr* out_font_render_style) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   if (font_service_.encountered_error()) {
     *out_valid = false;
@@ -313,7 +312,8 @@
     bool* out_valid,
     mojom::FontRenderStylePtr* out_font_render_style,
     mojom::FontRenderStylePtr font_render_style) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
 
   *out_valid = !font_render_style.is_null();
@@ -328,7 +328,7 @@
     bool* out_valid,
     std::string postscript_name_or_full_font_name,
     mojom::FontIdentityPtr* out_font_identity) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
   if (font_service_.encountered_error()) {
     *out_valid = false;
@@ -349,7 +349,8 @@
     bool* out_valid,
     mojom::FontIdentityPtr* out_font_identity,
     mojom::FontIdentityPtr font_identity) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
 
   *out_valid = !font_identity.is_null();
@@ -367,7 +368,8 @@
     uint32_t charset,
     uint32_t fallback_family_type,
     base::File* out_font_file_handle) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   *out_font_file_handle = base::File();
   if (font_service_.encountered_error()) {
     done_event->Signal();
@@ -384,7 +386,8 @@
     base::WaitableEvent* done_event,
     base::File* out_font_file_handle,
     base::File file) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
   pending_waitable_events_.erase(done_event);
 
   *out_font_file_handle = std::move(file);
@@ -405,9 +408,5 @@
                      weak_factory_.GetWeakPtr()));
 }
 
-void FontServiceThread::CleanUp() {
-  font_service_.reset();
-}
-
 }  // namespace internal
 }  // namespace font_service
diff --git a/components/services/font/public/cpp/font_service_thread.h b/components/services/font/public/cpp/font_service_thread.h
index 186a904..33fe6bd7 100644
--- a/components/services/font/public/cpp/font_service_thread.h
+++ b/components/services/font/public/cpp/font_service_thread.h
@@ -12,8 +12,6 @@
 #include "base/files/file.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/threading/thread.h"
-#include "base/threading/thread_checker.h"
 #include "components/services/font/public/interfaces/font_service.mojom.h"
 #include "third_party/skia/include/core/SkStream.h"
 #include "third_party/skia/include/core/SkTypeface.h"
@@ -31,8 +29,8 @@
 // of this mismatch, we create a thread which owns the mojo pipe, sends and
 // receives messages. The multiple threads which call through FontLoader class
 // do blocking message calls to this thread.
-class FontServiceThread : public base::Thread,
-                          public base::RefCountedThreadSafe<FontServiceThread> {
+// TODO(936569): Rename FontServiceThread since it's no longer a thread.
+class FontServiceThread : public base::RefCountedThreadSafe<FontServiceThread> {
  public:
   explicit FontServiceThread(mojom::FontServicePtr font_service);
 
@@ -72,7 +70,9 @@
 
  private:
   friend class base::RefCountedThreadSafe<FontServiceThread>;
-  ~FontServiceThread() override;
+  virtual ~FontServiceThread();
+
+  void Init();
 
   // Methods which run on the FontServiceThread. The public MatchFamilyName
   // calls this method, this method calls the mojo interface, and sets up the
@@ -169,17 +169,13 @@
   // thread.
   void OnFontServiceConnectionError();
 
-  // base::Thread
-  void Init() override;
-  void CleanUp() override;
-
   // This member is used to safely pass data from one thread to another. It is
   // set in the constructor and is consumed in Init().
-  mojo::InterfacePtrInfo<mojom::FontService> font_service_info_;
+  mojom::FontServicePtrInfo font_service_info_;
 
   // This member is set in Init(). It takes |font_service_info_|, which is
   // non-thread bound, and binds it to the newly created thread.
-  mojo::InterfacePtr<mojom::FontService> font_service_;
+  mojom::FontServicePtr font_service_;
 
   // All WaitableEvents supplied to OpenStreamImpl() and the other *Impl()
   // functions are added here while waiting on the response from the
@@ -190,8 +186,7 @@
   // never received.
   std::set<base::WaitableEvent*> pending_waitable_events_;
 
-  THREAD_CHECKER(thread_checker_);
-
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
   base::WeakPtrFactory<FontServiceThread> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(FontServiceThread);
diff --git a/components/tracing/common/tracing_sampler_profiler.cc b/components/tracing/common/tracing_sampler_profiler.cc
index dd043a8..f27aee7b 100644
--- a/components/tracing/common/tracing_sampler_profiler.cc
+++ b/components/tracing/common/tracing_sampler_profiler.cc
@@ -113,7 +113,7 @@
         frame_name = "Unknown";
 #else
     if (frame.module) {
-      module_name = frame.module->GetFilename().BaseName().MaybeAsASCII();
+      module_name = frame.module->GetDebugBasename().MaybeAsASCII();
       module_id = frame.module->GetId();
       frame_name = GetFrameNameFromOffsetAddr(frame.instruction_pointer -
                                               frame.module->GetBaseAddress());
diff --git a/components/viz/service/display_embedder/direct_context_provider.cc b/components/viz/service/display_embedder/direct_context_provider.cc
index a714fd9..3830a6ee 100644
--- a/components/viz/service/display_embedder/direct_context_provider.cc
+++ b/components/viz/service/display_embedder/direct_context_provider.cc
@@ -140,10 +140,12 @@
   // Get into known state (see
   // SkiaOutputSurfaceImplOnGpu::ScopedUseContextProvider).
   gles2_implementation_->BindFramebuffer(GL_FRAMEBUFFER, 0);
-  gles2_implementation_->Disable(GL_SCISSOR_TEST);
-  gles2_implementation_->Disable(GL_STENCIL_TEST);
-  gles2_implementation_->Disable(GL_BLEND);
-  gles2_implementation_->ActiveTexture(GL_TEXTURE0);
+
+  decoder_->RestoreActiveTexture();
+  decoder_->RestoreProgramBindings();
+  decoder_->RestoreAllAttributes();
+  decoder_->RestoreGlobalState();
+  decoder_->RestoreBufferBindings();
 
   if (texture_client_id) {
     if (!framebuffer_id_)
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 0b99a3f2..cfd219e1 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -82,10 +82,6 @@
     // side consistent with that.
     auto* api = impl_on_gpu_->api_;
     api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, 0);
-    api->glDisableFn(GL_SCISSOR_TEST);
-    api->glDisableFn(GL_STENCIL_TEST);
-    api->glDisableFn(GL_BLEND);
-    api->glActiveTextureFn(GL_TEXTURE0);
     impl_on_gpu_->context_provider_->SetGLRendererCopierRequiredState(
         texture_client_id);
   }
diff --git a/content/app/strings/content_strings.grd b/content/app/strings/content_strings.grd
index b264f04d..e279037 100644
--- a/content/app/strings/content_strings.grd
+++ b/content/app/strings/content_strings.grd
@@ -624,14 +624,8 @@
       <message name="IDS_AX_IMAGE_ANNOTATION_PENDING" desc="Accessibility message spoken out loud to screen reader users saying that the browser is in the middle of trying to get a description for an image.">
         Getting description...
       </message>
-      <message name="IDS_AX_IMAGE_ANNOTATION_EMPTY" desc="Accessibility message spoken out loud to screen reader users indicating that there is no description for this image.">
-        No description is available.
-      </message>
       <message name="IDS_AX_IMAGE_ANNOTATION_ADULT" desc="Accessibility message spoken out loud to screen reader users indicating that an image appears to be something like nudity or pornography or other policy violation that is not appropriate for children.">
-        Appears to be adult content.
-      </message>
-      <message name="IDS_AX_IMAGE_ANNOTATION_PROCESS_FAILED" desc="Accessibility message spoken out loud to screen reader users that the browser was not able to get a description for an image.">
-        Unable to get a description.
+        Appears to contain adult content. No description available.
       </message>
       <message name="IDS_AX_IMAGE_ANNOTATION_OCR_CONTEXT" desc="Accessibility message spoken out loud to screen reader users when reading text that was automatically extracted from an image, for an example the word 'stop' could be extracted from a photo of a stop sign. Because automatic text extraction sometimes contains errors it is important that the language used indicates some uncertainty - it may say 'stop' or it appears to say 'stop', not it definitely says 'stop'.">
         Appears to say: <ph name="OCR_TEXT">$1<ex>Stop</ex></ph>
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 41ef1ca..5ebd937 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -1272,15 +1272,11 @@
     case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
       message_id = IDS_AX_IMAGE_ANNOTATION_PENDING;
       break;
-    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
-      message_id = IDS_AX_IMAGE_ANNOTATION_EMPTY;
-      break;
     case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
       message_id = IDS_AX_IMAGE_ANNOTATION_ADULT;
       break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
     case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
-      message_id = IDS_AX_IMAGE_ANNOTATION_PROCESS_FAILED;
-      break;
     case ax::mojom::ImageAnnotationStatus::kNone:
     case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
     case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
diff --git a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
index 190dbfe..6421add 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
@@ -218,8 +218,8 @@
       ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
   expected_descriptions.push_back("ExistingLabel. Annotation");
 
-  // If the status is AnnotationEmpty, failure text should be appended
-  // to the name.
+  // If the status is AnnotationEmpty, no failure text should be added to the
+  // name.
   tree.nodes[7].id = 8;
   tree.nodes[7].role = ax::mojom::Role::kImage;
   tree.nodes[7].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
@@ -227,8 +227,7 @@
   tree.nodes[7].SetName("ExistingLabel");
   tree.nodes[7].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
-  expected_descriptions.push_back(
-      "ExistingLabel. No description is available.");
+  expected_descriptions.push_back("ExistingLabel");
 
   // If the status is AnnotationAdult, appropriate text should be appended
   // to the name.
@@ -239,11 +238,11 @@
   tree.nodes[8].SetName("ExistingLabel");
   tree.nodes[8].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationAdult);
-  expected_descriptions.push_back(
-      "ExistingLabel. Appears to be adult content.");
+  expected_descriptions.push_back("ExistingLabel. Appears to contain adult "
+                                  "content. No description available.");
 
-  // If the status is AnnotationProcessFailed, appropriate text should be
-  // appended to the name.
+  // If the status is AnnotationProcessFailed, no failure text should be added
+  // to the name.
   tree.nodes[9].id = 10;
   tree.nodes[9].role = ax::mojom::Role::kImage;
   tree.nodes[9].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
@@ -251,8 +250,7 @@
   tree.nodes[9].SetName("ExistingLabel");
   tree.nodes[9].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed);
-  expected_descriptions.push_back(
-      "ExistingLabel. Unable to get a description.");
+  expected_descriptions.push_back("ExistingLabel");
 
   // We should have one expected description per child of the root.
   ASSERT_EQ(expected_descriptions.size(), tree.nodes[0].child_ids.size());
diff --git a/content/browser/cache_storage/cache_storage.cc b/content/browser/cache_storage/cache_storage.cc
index 1d1df93..9e0249a6 100644
--- a/content/browser/cache_storage/cache_storage.cc
+++ b/content/browser/cache_storage/cache_storage.cc
@@ -16,7 +16,6 @@
 #include "base/files/file_util.h"
 #include "base/files/memory_mapped_file.h"
 #include "base/guid.h"
-#include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
@@ -43,10 +42,10 @@
 #include "net/base/directory_lister.h"
 #include "net/base/net_errors.h"
 #include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/quota/padding_key.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
-using base::LazyInstance;
 using blink::mojom::CacheStorageError;
 using blink::mojom::StorageType;
 using crypto::SymmetricKey;
@@ -55,31 +54,6 @@
 
 namespace {
 
-const SymmetricKey::Algorithm kPaddingKeyAlgorithm = SymmetricKey::AES;
-
-// LazyInstance needs a new-able type so this class exists solely to "own"
-// a SymmetricKey.
-class SymmetricKeyOwner {
- public:
-  std::unique_ptr<SymmetricKey> CreateDuplicate() const {
-    return SymmetricKey::Import(kPaddingKeyAlgorithm, key());
-  }
-
-  // Only for test purposes.
-  void GenerateNew() {
-    key_ = SymmetricKey::GenerateRandomKey(kPaddingKeyAlgorithm, 128);
-  }
-
-  const std::string& key() const { return key_->key(); }
-
- private:
-  std::unique_ptr<SymmetricKey> key_ =
-      SymmetricKey::GenerateRandomKey(kPaddingKeyAlgorithm, 128);
-};
-
-static LazyInstance<SymmetricKeyOwner>::Leaky s_padding_key =
-    LAZY_INSTANCE_INITIALIZER;
-
 std::string HexedHash(const std::string& value) {
   std::string value_hash = base::SHA1HashString(value);
   std::string valued_hexed_hash = base::ToLowerASCII(
@@ -93,10 +67,6 @@
       FROM_HERE, base::BindOnce(std::move(callback), *accumulator));
 }
 
-std::unique_ptr<SymmetricKey> ImportPaddingKey(const std::string& raw_key) {
-  return SymmetricKey::Import(kPaddingKeyAlgorithm, raw_key);
-}
-
 }  // namespace
 
 const char CacheStorage::kIndexFileName[] = "index.txt";
@@ -211,14 +181,14 @@
       std::unique_ptr<SymmetricKey> cache_padding_key) override {
     return CacheStorageCache::CreateMemoryCache(
         origin_, owner_, cache_name, cache_storage_, quota_manager_proxy_,
-        blob_context_, s_padding_key.Get().CreateDuplicate());
+        blob_context_, storage::CopyDefaultPaddingKey());
   }
 
   void PrepareNewCacheDestination(const std::string& cache_name,
                                   CacheCallback callback) override {
     std::unique_ptr<CacheStorageCache> cache =
         CreateCache(cache_name, 0 /*cache_size*/, 0 /* cache_padding */,
-                    s_padding_key.Get().CreateDuplicate());
+                    storage::CopyDefaultPaddingKey());
     std::move(callback).Run(std::move(cache));
   }
 
@@ -326,7 +296,7 @@
     cache_name_to_cache_dir_[cache_name] = cache_dir;
     std::move(callback).Run(CreateCache(cache_name, CacheStorage::kSizeUnknown,
                                         CacheStorage::kSizeUnknown,
-                                        s_padding_key.Get().CreateDuplicate()));
+                                        storage::CopyDefaultPaddingKey()));
   }
 
   void CleanUpDeletedCache(CacheStorageCache* cache) override {
@@ -444,9 +414,9 @@
         cache_padding = CacheStorage::kSizeUnknown;
       }
 
-      std::string cache_padding_key = cache.has_padding_key()
-                                          ? cache.padding_key()
-                                          : s_padding_key.Get().key();
+      std::string cache_padding_key =
+          cache.has_padding_key() ? cache.padding_key()
+                                  : storage::SerializeDefaultPaddingKey();
 
       index->Insert(CacheStorageIndex::CacheMetadata(
           cache.name(), cache_size, cache_padding,
@@ -854,11 +824,6 @@
   scheduler_->CompleteOperationAndRunNext();
 }
 
-// static
-void CacheStorage::GenerateNewKeyForTesting() {
-  s_padding_key.Get().GenerateNew();
-}
-
 // Init is run lazily so that it is called on the proper MessageLoop.
 void CacheStorage::LazyInit() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -1167,7 +1132,7 @@
     DCHECK(metadata);
     std::unique_ptr<CacheStorageCache> new_cache = cache_loader_->CreateCache(
         cache_name, metadata->size, metadata->padding,
-        ImportPaddingKey(metadata->padding_key));
+        storage::DeserializePaddingKey(metadata->padding_key));
     CacheStorageCache* cache_ptr = new_cache.get();
     map_iter->second = std::move(new_cache);
 
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index 172a80b..df91843a 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -51,6 +51,7 @@
 #include "storage/browser/blob/blob_impl.h"
 #include "storage/browser/blob/blob_storage_context.h"
 #include "storage/browser/blob/blob_url_request_job_factory.h"
+#include "storage/browser/quota/padding_key.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/browser/test/mock_quota_manager_proxy.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
@@ -1346,9 +1347,9 @@
   EXPECT_TRUE(FlushCacheStorageIndex(origin1_));
   DestroyStorageManager();
 
-  // GenerateNewKeyForTest isn't thread safe so
+  // ResetPaddingKeyForTesting isn't thread safe so
   base::RunLoop().RunUntilIdle();
-  CacheStorage::GenerateNewKeyForTesting();
+  storage::ResetPaddingKeyForTesting();
 
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   CreateStorageManager();
diff --git a/content/browser/devtools/protocol/memory_handler.cc b/content/browser/devtools/protocol/memory_handler.cc
index 50e5a35..af4fcb5e 100644
--- a/content/browser/devtools/protocol/memory_handler.cc
+++ b/content/browser/devtools/protocol/memory_handler.cc
@@ -61,8 +61,8 @@
   for (const auto* module : module_cache.GetModules()) {
     modules->addItem(
         Memory::Module::Create()
-            .SetName(base::StringPrintf("%" PRFilePath,
-                                        module->GetFilename().value().c_str()))
+            .SetName(base::StringPrintf(
+                "%" PRFilePath, module->GetDebugBasename().value().c_str()))
             .SetUuid(module->GetId())
             .SetBaseAddress(
                 base::StringPrintf("0x%" PRIxPTR, module->GetBaseAddress()))
diff --git a/content/browser/idle/idle_manager.cc b/content/browser/idle/idle_manager.cc
index e6d47d35..383f21a7e 100644
--- a/content/browser/idle/idle_manager.cc
+++ b/content/browser/idle/idle_manager.cc
@@ -27,11 +27,13 @@
   DefaultIdleProvider() = default;
   ~DefaultIdleProvider() override = default;
 
-  ui::IdleState CalculateIdleState(int idle_threshold) override {
-    return ui::CalculateIdleState(idle_threshold);
+  ui::IdleState CalculateIdleState(base::TimeDelta idle_threshold) override {
+    return ui::CalculateIdleState(idle_threshold.InSeconds());
   }
 
-  int CalculateIdleTime() override { return ui::CalculateIdleTime(); }
+  base::TimeDelta CalculateIdleTime() override {
+    return base::TimeDelta::FromSeconds(ui::CalculateIdleTime());
+  }
 
   bool CheckIdleStateIsLocked() override {
     return ui::CheckIdleStateIsLocked();
@@ -39,8 +41,8 @@
 };
 
 blink::mojom::IdleStatePtr IdleTimeToIdleState(bool locked,
-                                               int idle_time,
-                                               int idle_threshold) {
+                                               base::TimeDelta idle_time,
+                                               base::TimeDelta idle_threshold) {
   blink::mojom::UserIdleState user;
   if (idle_time >= idle_threshold)
     user = blink::mojom::UserIdleState::kIdle;
@@ -78,11 +80,11 @@
   bindings_.AddBinding(this, std::move(request));
 }
 
-void IdleManager::AddMonitor(uint32_t threshold,
+void IdleManager::AddMonitor(base::TimeDelta threshold,
                              blink::mojom::IdleMonitorPtr monitor_ptr,
                              AddMonitorCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (threshold == 0) {
+  if (threshold.is_zero()) {
     mojo::ReportBadMessage("Invalid threshold");
     return;
   }
@@ -138,8 +140,9 @@
   poll_timer_.Stop();
 }
 
-blink::mojom::IdleStatePtr IdleManager::CheckIdleState(int threshold) {
-  int idle_time = idle_time_provider_->CalculateIdleTime();
+blink::mojom::IdleStatePtr IdleManager::CheckIdleState(
+    base::TimeDelta threshold) {
+  base::TimeDelta idle_time = idle_time_provider_->CalculateIdleTime();
   bool locked = idle_time_provider_->CheckIdleStateIsLocked();
 
   return IdleTimeToIdleState(locked, idle_time, threshold);
diff --git a/content/browser/idle/idle_manager.h b/content/browser/idle/idle_manager.h
index dfdf4f5..2f8a9b0 100644
--- a/content/browser/idle/idle_manager.h
+++ b/content/browser/idle/idle_manager.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "content/browser/idle/idle_monitor.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
@@ -37,8 +38,9 @@
     // See ui/base/idle/idle.h for the semantics of these methods.
     // TODO(goto): should this be made private? Doesn't seem to be necessary
     // as part of a public interface.
-    virtual ui::IdleState CalculateIdleState(int idle_threshold) = 0;
-    virtual int CalculateIdleTime() = 0;
+    virtual ui::IdleState CalculateIdleState(
+        base::TimeDelta idle_threshold) = 0;
+    virtual base::TimeDelta CalculateIdleTime() = 0;
     virtual bool CheckIdleStateIsLocked() = 0;
 
    private:
@@ -53,7 +55,7 @@
                      const url::Origin& origin);
 
   // blink.mojom.IdleManager:
-  void AddMonitor(uint32_t threshold,
+  void AddMonitor(base::TimeDelta threshold,
                   blink::mojom::IdleMonitorPtr monitor_ptr,
                   AddMonitorCallback callback) override;
 
@@ -84,7 +86,7 @@
   // Callback for the async state query. Updates monitors as needed.
   void UpdateIdleStateCallback(int idle_time);
 
-  blink::mojom::IdleStatePtr CheckIdleState(int threshold);
+  blink::mojom::IdleStatePtr CheckIdleState(base::TimeDelta threshold);
 
   base::RepeatingTimer poll_timer_;
   std::unique_ptr<IdleTimeProvider> idle_time_provider_;
diff --git a/content/browser/idle/idle_manager_unittest.cc b/content/browser/idle/idle_manager_unittest.cc
index 8c6547c..cd717f03 100644
--- a/content/browser/idle/idle_manager_unittest.cc
+++ b/content/browser/idle/idle_manager_unittest.cc
@@ -4,10 +4,13 @@
 
 #include "content/browser/idle/idle_manager.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "base/time/time.h"
 #include "content/browser/permissions/permission_controller_impl.h"
 #include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
@@ -36,7 +39,7 @@
 
 namespace {
 
-const uint32_t kTresholdInSecs = 10;
+constexpr base::TimeDelta kTreshold = base::TimeDelta::FromSeconds(10);
 
 class MockIdleMonitor : public blink::mojom::IdleMonitor {
  public:
@@ -48,8 +51,8 @@
   MockIdleTimeProvider() = default;
   ~MockIdleTimeProvider() override = default;
 
-  MOCK_METHOD1(CalculateIdleState, ui::IdleState(int));
-  MOCK_METHOD0(CalculateIdleTime, int());
+  MOCK_METHOD1(CalculateIdleState, ui::IdleState(base::TimeDelta));
+  MOCK_METHOD0(CalculateIdleTime, base::TimeDelta());
   MOCK_METHOD0(CheckIdleStateIsLocked, bool());
 
  private:
@@ -90,12 +93,13 @@
   }));
 
   // Initial state of the system.
-  EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(0));
+  EXPECT_CALL(*mock, CalculateIdleTime())
+      .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(0)));
   EXPECT_CALL(*mock, CheckIdleStateIsLocked())
       .WillRepeatedly(testing::Return(false));
 
   service_ptr->AddMonitor(
-      kTresholdInSecs, std::move(monitor_ptr),
+      kTreshold, std::move(monitor_ptr),
       base::BindOnce(
           [](base::OnceClosure callback, blink::mojom::IdleStatePtr state) {
             // The initial state of the status of the user is to be active.
@@ -128,10 +132,11 @@
   {
     base::RunLoop loop;
     // Initial state of the system.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(0));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(0)));
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
           EXPECT_EQ(blink::mojom::UserIdleState::kActive, state->user);
           loop.Quit();
@@ -143,7 +148,8 @@
   {
     base::RunLoop loop;
     // Simulates a user going idle.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(10));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(10)));
 
     // Expects Update to be notified about the change to idle.
     EXPECT_CALL(monitor, Update(_))
@@ -157,7 +163,8 @@
   {
     base::RunLoop loop;
     // Simulates a user going active, calling a callback under the threshold.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(0));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(0)));
 
     // Expects Update to be notified about the change to active.
     // auto quit = loop.QuitClosure();
@@ -196,7 +203,7 @@
         .WillRepeatedly(testing::Return(true));
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
           EXPECT_EQ(blink::mojom::ScreenIdleState::kLocked, state->screen);
           loop.Quit();
@@ -248,7 +255,7 @@
         .WillRepeatedly(testing::Return(false));
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
           EXPECT_EQ(blink::mojom::ScreenIdleState::kUnlocked, state->screen);
           loop.Quit();
@@ -300,7 +307,7 @@
         .WillRepeatedly(testing::Return(false));
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
           EXPECT_EQ(blink::mojom::UserIdleState::kActive, state->user);
           EXPECT_EQ(blink::mojom::ScreenIdleState::kUnlocked, state->screen);
@@ -332,7 +339,8 @@
     base::RunLoop loop;
 
     // Simulates a user going idle, whilte the screen is still locked.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(10));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(10)));
     EXPECT_CALL(*mock, CheckIdleStateIsLocked())
         .WillRepeatedly(testing::Return(true));
 
@@ -370,12 +378,13 @@
     base::RunLoop loop;
 
     // Simulates a user going idle, but with the screen still unlocked.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(0));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(0)));
     EXPECT_CALL(*mock, CheckIdleStateIsLocked())
         .WillRepeatedly(testing::Return(false));
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
           EXPECT_EQ(blink::mojom::UserIdleState::kActive, state->user);
           EXPECT_EQ(blink::mojom::ScreenIdleState::kUnlocked, state->screen);
@@ -388,7 +397,8 @@
   {
     base::RunLoop loop;
     // Simulates a user going idle, but with the screen still unlocked.
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(10));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(10)));
     EXPECT_CALL(*mock, CheckIdleStateIsLocked())
         .WillRepeatedly(testing::Return(false));
 
@@ -408,7 +418,8 @@
     // Simulates the screeng getting locked by the system after the user goes
     // idle (e.g. screensaver kicks in first, throwing idleness, then getting
     // locked).
-    EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(10));
+    EXPECT_CALL(*mock, CalculateIdleTime())
+        .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(10)));
     EXPECT_CALL(*mock, CheckIdleStateIsLocked())
         .WillRepeatedly(testing::Return(true));
 
@@ -448,7 +459,7 @@
     base::RunLoop loop;
 
     service_ptr->AddMonitor(
-        kTresholdInSecs, std::move(monitor_ptr),
+        kTreshold, std::move(monitor_ptr),
         base::BindLambdaForTesting(
             [&](blink::mojom::IdleStatePtr state) { loop.Quit(); }));
 
@@ -486,12 +497,13 @@
   base::RunLoop loop;
 
   // Initial state of the system.
-  EXPECT_CALL(*mock, CalculateIdleTime()).WillRepeatedly(testing::Return(6));
+  EXPECT_CALL(*mock, CalculateIdleTime())
+      .WillRepeatedly(testing::Return(base::TimeDelta::FromSeconds(6)));
   EXPECT_CALL(*mock, CheckIdleStateIsLocked())
       .WillRepeatedly(testing::Return(false));
 
   service_ptr->AddMonitor(
-      5, std::move(monitor_ptr),
+      base::TimeDelta::FromSeconds(5), std::move(monitor_ptr),
       base::BindLambdaForTesting([&](blink::mojom::IdleStatePtr state) {
         EXPECT_EQ(blink::mojom::UserIdleState::kIdle, state->user);
         loop.Quit();
diff --git a/content/browser/idle/idle_monitor.cc b/content/browser/idle/idle_monitor.cc
index 48afd1d..ea332b9 100644
--- a/content/browser/idle/idle_monitor.cc
+++ b/content/browser/idle/idle_monitor.cc
@@ -17,7 +17,7 @@
 
 IdleMonitor::IdleMonitor(blink::mojom::IdleMonitorPtr monitor,
                          blink::mojom::IdleStatePtr last_state,
-                         uint32_t threshold)
+                         base::TimeDelta threshold)
     : client_(std::move(monitor)),
       last_state_(std::move(last_state)),
       threshold_(threshold) {}
diff --git a/content/browser/idle/idle_monitor.h b/content/browser/idle/idle_monitor.h
index 172ad99..7609854 100644
--- a/content/browser/idle/idle_monitor.h
+++ b/content/browser/idle/idle_monitor.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/connection_error_callback.h"
@@ -25,14 +26,14 @@
  public:
   IdleMonitor(blink::mojom::IdleMonitorPtr monitor,
               blink::mojom::IdleStatePtr last_state,
-              uint32_t threshold);
+              base::TimeDelta threshold);
   ~IdleMonitor();
 
   const blink::mojom::IdleState& last_state() const {
     return *last_state_.get();
   }
-  uint32_t threshold() const { return threshold_; }
-  void set_threshold(uint32_t threshold) { threshold_ = threshold; }
+  base::TimeDelta threshold() const { return threshold_; }
+  void set_threshold(base::TimeDelta threshold) { threshold_ = threshold; }
 
   void SetLastState(blink::mojom::IdleStatePtr state);
   void SetErrorHandler(base::OnceCallback<void(content::IdleMonitor*)> handler);
@@ -40,7 +41,7 @@
  private:
   blink::mojom::IdleMonitorPtr client_;
   blink::mojom::IdleStatePtr last_state_;
-  uint32_t threshold_;
+  base::TimeDelta threshold_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index edc10efb..30e8b6d6 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -5,6 +5,7 @@
 #include "content/browser/loader/navigation_url_loader_impl.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -136,13 +137,6 @@
          signed_exchange_utils::IsSignedExchangeHandlingEnabled();
 }
 
-// Request ID for browser initiated requests. We start at -2 on the same lines
-// as ResourceDispatcherHostImpl.
-int g_next_request_id = -2;
-GlobalRequestID MakeGlobalRequestID() {
-  return GlobalRequestID(-1, g_next_request_id--);
-}
-
 size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) {
   base::Pickle cert_pickle;
   ssl_info.cert->Persist(&cert_pickle);
@@ -381,29 +375,6 @@
   mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_;
 };
 
-// Creates a URLLoaderFactory that uses |header_client|. This should have the
-// same settings as the factory from the URLLoaderFactoryGetter.
-std::unique_ptr<network::SharedURLLoaderFactoryInfo>
-CreateNetworkFactoryInfoWithHeaderClient(
-    network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
-    StoragePartitionImpl* partition) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  network::mojom::URLLoaderFactoryPtrInfo factory_info;
-  network::mojom::URLLoaderFactoryParamsPtr params =
-      network::mojom::URLLoaderFactoryParams::New();
-  params->header_client = std::move(header_client);
-  params->process_id = network::mojom::kBrowserProcessId;
-  params->is_corb_enabled = false;
-  params->disable_web_security =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kDisableWebSecurity);
-
-  partition->GetNetworkContext()->CreateURLLoaderFactory(
-      mojo::MakeRequest(&factory_info), std::move(params));
-  return std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>(
-      std::move(factory_info));
-}
-
 }  // namespace
 
 // Kept around during the lifetime of the navigation request, and is
@@ -1741,8 +1712,12 @@
       partition->url_loader_factory_getter()->GetNetworkFactoryInfo();
   if (header_client) {
     needs_loader_factory_interceptor = true;
-    network_factory_info = CreateNetworkFactoryInfoWithHeaderClient(
-        std::move(header_client), partition);
+    network::mojom::URLLoaderFactoryPtrInfo factory_info;
+    CreateURLLoaderFactoryWithHeaderClient(
+        std::move(header_client), mojo::MakeRequest(&factory_info), partition);
+    network_factory_info =
+        std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>(
+            std::move(factory_info));
   }
 
   DCHECK(!request_controller_);
@@ -1838,6 +1813,7 @@
   delegate_->OnRequestFailed(status);
 }
 
+// static
 void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting(
     const BeginNavigationInterceptor& interceptor) {
   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) ||
@@ -1846,6 +1822,7 @@
   g_interceptor.Get() = interceptor;
 }
 
+// static
 void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting(
     const URLLoaderFactoryInterceptor& interceptor) {
   DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) ||
@@ -1854,6 +1831,33 @@
   g_loader_factory_interceptor.Get() = interceptor;
 }
 
+// static
+void NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient(
+    network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
+    network::mojom::URLLoaderFactoryRequest factory_request,
+    StoragePartitionImpl* partition) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  network::mojom::URLLoaderFactoryParamsPtr params =
+      network::mojom::URLLoaderFactoryParams::New();
+  params->header_client = std::move(header_client);
+  params->process_id = network::mojom::kBrowserProcessId;
+  params->is_corb_enabled = false;
+  params->disable_web_security =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kDisableWebSecurity);
+
+  partition->GetNetworkContext()->CreateURLLoaderFactory(
+      std::move(factory_request), std::move(params));
+}
+
+// static
+GlobalRequestID NavigationURLLoaderImpl::MakeGlobalRequestID() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  // Start at -2 on the same lines as ResourceDispatcherHostImpl.
+  static int s_next_request_id = -2;
+  return GlobalRequestID(-1, s_next_request_id--);
+}
+
 void NavigationURLLoaderImpl::OnRequestStarted(base::TimeTicks timestamp) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   delegate_->OnRequestStarted(timestamp);
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index 2afea45..b993353 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -27,6 +27,7 @@
 class NavigationLoaderInterceptor;
 class ResourceContext;
 class StoragePartition;
+class StoragePartitionImpl;
 struct GlobalRequestID;
 
 class CONTENT_EXPORT NavigationURLLoaderImpl : public NavigationURLLoader {
@@ -90,6 +91,18 @@
   static void SetURLLoaderFactoryInterceptorForTesting(
       const URLLoaderFactoryInterceptor& interceptor);
 
+  // Creates a URLLoaderFactory for a navigation. The factory uses
+  // |header_client|. This should have the same settings as the factory from the
+  // URLLoaderFactoryGetter. Called on the UI thread.
+  static void CreateURLLoaderFactoryWithHeaderClient(
+      network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
+      network::mojom::URLLoaderFactoryRequest factory_request,
+      StoragePartitionImpl* partition);
+
+  // Returns a Request ID for browser-initiated navigation requests. Called on
+  // the IO thread.
+  static GlobalRequestID MakeGlobalRequestID();
+
  private:
   class URLLoaderRequestController;
   void OnRequestStarted(base::TimeTicks timestamp);
diff --git a/content/browser/renderer_host/input/scroll_latency_browsertest.cc b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
index 5f808f6..34f496a 100644
--- a/content/browser/renderer_host/input/scroll_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "cc/base/switches.h"
 #include "content/browser/renderer_host/input/synthetic_gesture.h"
 #include "content/browser/renderer_host/input/synthetic_gesture_controller.h"
 #include "content/browser/renderer_host/input/synthetic_gesture_target.h"
@@ -18,6 +19,7 @@
 #include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host_view.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -32,8 +34,6 @@
     "<html>"
     "<head>"
     "<title>Scroll latency histograms browsertests.</title>"
-    "<script src=\"../../resources/testharness.js\"></script>"
-    "<script src=\"../../resources/testharnessreport.js\"></script>"
     "<style>"
     "body {"
     "  height:3000px;"
@@ -56,7 +56,6 @@
     "spin();"
     "</script>"
     "</html>";
-
 }  // namespace
 
 namespace content {
@@ -87,6 +86,18 @@
   }
 
  protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    ContentBrowserTest::SetUpCommandLine(command_line);
+    if (disable_threaded_scrolling_) {
+      command_line->AppendSwitch(::switches::kDisableThreadedScrolling);
+    }
+    // Set the scroll animation duration to a large number so that
+    // we ensure secondary GestureScrollUpdates update the animation
+    // instead of starting a new one.
+    command_line->AppendSwitchASCII(
+        cc::switches::kCCScrollAnimationDurationForTesting, "10000000");
+  }
+
   void LoadURL() {
     const GURL data_url(kDataURL);
     NavigateToURL(shell(), data_url);
@@ -100,8 +111,9 @@
     observer.WaitForHitTestData();
   }
 
-  // Generate a single wheel tick, scrolling by |distance|. This will perform a
-  // smooth scroll on platforms which support it.
+  // Generate the gestures associated with kNumWheelScrolls ticks,
+  // scrolling by |distance|. This will perform a smooth scroll on platforms
+  // which support it.
   void DoSmoothWheelScroll(const gfx::Vector2d& distance) {
     blink::WebGestureEvent event =
         SyntheticWebGestureEventBuilder::BuildScrollBegin(
@@ -111,13 +123,54 @@
         blink::WebGestureEvent::ScrollUnits::kPixels;
     GetWidgetHost()->ForwardGestureEvent(event);
 
-    blink::WebGestureEvent event2 =
-        SyntheticWebGestureEventBuilder::BuildScrollUpdate(
-            distance.x(), -distance.y(), 0,
-            blink::WebGestureDevice::kWebGestureDeviceTouchpad);
-    event2.data.scroll_update.delta_units =
-        blink::WebGestureEvent::ScrollUnits::kPixels;
-    GetWidgetHost()->ForwardGestureEvent(event2);
+    const uint32_t kNumWheelScrolls = 2;
+    for (uint32_t i = 0; i < kNumWheelScrolls; i++) {
+      // Install a VisualStateCallback and wait for the callback in response
+      // to each GestureScrollUpdate before sending the next GSU. This will
+      // ensure the events are not coalesced (resulting in fewer end-to-end
+      // latency histograms being logged).
+      // We must install a callback for each gesture since they are one-shot
+      // callbacks.
+      shell()->web_contents()->GetMainFrame()->InsertVisualStateCallback(
+          base::BindOnce(&ScrollLatencyBrowserTest::InvokeVisualStateCallback,
+                         base::Unretained(this)));
+
+      blink::WebGestureEvent event2 =
+          SyntheticWebGestureEventBuilder::BuildScrollUpdate(
+              distance.x(), -distance.y(), 0,
+              blink::WebGestureDevice::kWebGestureDeviceTouchpad);
+      event2.data.scroll_update.delta_units =
+          blink::WebGestureEvent::ScrollUnits::kPixels;
+      GetWidgetHost()->ForwardGestureEvent(event2);
+
+      while (visual_state_callback_count_ <= i) {
+        // TODO: There's currently no way to block until a GPU swap
+        // completes. Until then we need to spin and wait. See
+        // crbug.com/897520 for more details.
+        GiveItSomeTime();
+      }
+    }
+  }
+
+  void InvokeVisualStateCallback(bool result) {
+    EXPECT_TRUE(result);
+    visual_state_callback_count_++;
+  }
+
+  void RunMultipleWheelScroll() {
+    DoSmoothWheelScroll(gfx::Vector2d(0, 100));
+    // We expect to see one ScrollBegin and two ScrollUpdate swap values.
+    while (!VerifyRecordedSamplesForHistogram(
+        1, "Event.Latency.ScrollBegin.Wheel.TimeToScrollUpdateSwapBegin4")) {
+      GiveItSomeTime();
+      FetchHistogramsFromChildProcesses();
+    }
+
+    while (!VerifyRecordedSamplesForHistogram(
+        1, "Event.Latency.ScrollUpdate.Wheel.TimeToScrollUpdateSwapBegin4")) {
+      GiveItSomeTime();
+      FetchHistogramsFromChildProcesses();
+    }
   }
 
   // Returns true if the given histogram has recorded the expected number of
@@ -130,24 +183,26 @@
   }
 
   std::unique_ptr<base::RunLoop> run_loop_;
+  bool disable_threaded_scrolling_ = false;
 
  private:
   base::HistogramTester histogram_tester_;
+  uint32_t visual_state_callback_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(ScrollLatencyBrowserTest);
 };
 
 // Perform a smooth wheel scroll, and verify that our end-to-end wheel latency
-// metric is recorded. See crbug.com/599910 for details.
-IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest, SmoothWheelScroll) {
+// metrics are recorded. See crbug.com/599910 for details.
+IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest, MultipleWheelScroll) {
   LoadURL();
+  RunMultipleWheelScroll();
+}
 
-  DoSmoothWheelScroll(gfx::Vector2d(0, 100));
-  while (!VerifyRecordedSamplesForHistogram(
-      1, "Event.Latency.ScrollBegin.Wheel.TimeToScrollUpdateSwapBegin4")) {
-    GiveItSomeTime();
-    FetchHistogramsFromChildProcesses();
-  }
+IN_PROC_BROWSER_TEST_F(ScrollLatencyBrowserTest, MultipleWheelScrollOnMain) {
+  disable_threaded_scrolling_ = true;
+  LoadURL();
+  RunMultipleWheelScroll();
 }
 
 // Do an upward wheel scroll, and verify that no scroll metrics is recorded when
diff --git a/content/browser/renderer_host/input/touch_action_filter.cc b/content/browser/renderer_host/input/touch_action_filter.cc
index 507f34c..fe13832 100644
--- a/content/browser/renderer_host/input/touch_action_filter.cc
+++ b/content/browser/renderer_host/input/touch_action_filter.cc
@@ -172,7 +172,6 @@
       }
 
       gesture_sequence_.append("U");
-      if (!compositor_touch_action_enabled_)
       // Scrolls restricted to a specific axis shouldn't permit movement
       // in the perpendicular axis.
       //
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 792ec87a..913408f 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3068,6 +3068,7 @@
     switches::kVModule,
     // Please keep these in alphabetical order. Compositor switches here should
     // also be added to chrome/browser/chromeos/login/chrome_restart_request.cc.
+    cc::switches::kCCScrollAnimationDurationForTesting,
     cc::switches::kCheckDamageEarly,
     cc::switches::kDisableCheckerImaging,
     cc::switches::kDisableCompositedAntialiasing,
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 88fd859..aba871a 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1337,6 +1337,14 @@
     SyntheticGestureParams::GestureType type,
     SyntheticGestureParams::GestureSourceType source,
     base::OnceClosure callback) {
+  // TODO(bokan): The RequestPresentationCallback mechanism doesn't seem to
+  // work in OOPIFs. For now, just callback immediately. Remove when fixed.
+  // https://crbug.com/924646.
+  if (GetView()->IsRenderWidgetHostViewChildFrame()) {
+    std::move(callback).Run();
+    return;
+  }
+
   // TODO(bokan): Input can be queued and delayed in InputRouterImpl based on
   // the kind of events we're getting. To be truly robust, we should wait until
   // those queues are flushed before issuing this message. This will be done in
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
index 99401257..e6785f1 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -18,18 +18,25 @@
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/devtools/service_worker_devtools_agent_host.h"
 #include "content/browser/devtools/service_worker_devtools_manager.h"
+#include "content/browser/loader/navigation_url_loader_impl.h"
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
 #include "content/browser/loader/resource_request_info_impl.h"
 #include "content/browser/loader/resource_requester_info.h"
 #include "content/browser/loader/url_loader_factory_impl.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_metrics.h"
 #include "content/browser/service_worker/service_worker_version.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "content/common/service_worker/service_worker_types.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/navigation_policy.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
@@ -40,6 +47,8 @@
 #include "net/log/net_log_event_type.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "net/url_request/url_request.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
 #include "services/network/throttling/throttling_controller.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/common/service_worker/service_worker_utils.h"
@@ -319,6 +328,48 @@
   }
 }
 
+// Creates the network URLLoaderFactory for the navigation preload request.
+void CreateNetworkFactoryForNavigationPreloadOnUI(
+    const ServiceWorkerFetchDispatcher::WebContentsGetter& web_contents_getter,
+    scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
+    network::mojom::URLLoaderFactoryRequest request) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
+
+  WebContents* web_contents = web_contents_getter.Run();
+  StoragePartitionImpl* partition = context_wrapper->storage_partition();
+  if (!web_contents || !partition) {
+    // The navigation was cancelled or we are in shutdown. Just drop the
+    // request. Otherwise, we might go to network without consulting the
+    // embedder first, which would break guarantees.
+    return;
+  }
+
+  // Follow what NavigationURLLoaderImpl does for the initiator passed to
+  // WillCreateURLLoaderFactory():
+  // Navigation requests are not associated with any particular
+  // |network::ResourceRequest::request_initiator| origin - using an opaque
+  // origin instead.
+  url::Origin initiator = url::Origin();
+
+  // We ignore the value of |bypass_redirect_checks_unused| since a redirects is
+  // just relayed to the service worker where preloadResponse is resolved as
+  // redirect.
+  bool bypass_redirect_checks_unused;
+
+  // Consult the embedder.
+  network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client;
+  GetContentClient()->browser()->WillCreateURLLoaderFactory(
+      web_contents->GetBrowserContext(), web_contents->GetMainFrame(),
+      web_contents->GetMainFrame()->GetProcess()->GetID(),
+      true /* is_navigation */, false /* is_download */, initiator, &request,
+      &header_client, &bypass_redirect_checks_unused);
+
+  // Make the network factory.
+  NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient(
+      std::move(header_client), std::move(request), partition);
+}
+
 }  // namespace
 
 // ResponseCallback is owned by the callback that is passed to
@@ -405,11 +456,18 @@
 class ServiceWorkerFetchDispatcher::URLLoaderAssets
     : public base::RefCounted<ServiceWorkerFetchDispatcher::URLLoaderAssets> {
  public:
+  // Non-NetworkService.
   URLLoaderAssets(
       std::unique_ptr<network::mojom::URLLoaderFactory> url_loader_factory,
       std::unique_ptr<DelegatingURLLoaderClient> url_loader_client)
       : url_loader_factory_(std::move(url_loader_factory)),
         url_loader_client_(std::move(url_loader_client)) {}
+  // NetworkService.
+  URLLoaderAssets(
+      scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
+      std::unique_ptr<DelegatingURLLoaderClient> url_loader_client)
+      : shared_url_loader_factory_(std::move(shared_url_loader_factory)),
+        url_loader_client_(std::move(url_loader_client)) {}
 
   void MaybeReportToDevTools(std::pair<int, int> worker_id,
                              int fetch_event_id) {
@@ -420,7 +478,13 @@
   friend class base::RefCounted<URLLoaderAssets>;
   virtual ~URLLoaderAssets() {}
 
+  // Non-NetworkService:
   std::unique_ptr<network::mojom::URLLoaderFactory> url_loader_factory_;
+
+  // NetworkService:
+  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
+
+  // Both:
   std::unique_ptr<DelegatingURLLoaderClient> url_loader_client_;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoaderAssets);
@@ -650,9 +714,7 @@
       Referrer::ReferrerPolicyForUrlRequest(original_info->GetReferrerPolicy());
   request.is_prerendering = original_info->IsPrerendering();
   request.load_flags = original_request->load_flags();
-  // Set to SUB_RESOURCE because we shouldn't trigger NavigationResourceThrottle
-  // for the service worker navigation preload request.
-  request.resource_type = RESOURCE_TYPE_SUB_RESOURCE;
+  request.resource_type = RESOURCE_TYPE_NAVIGATION_PRELOAD;
   request.priority = original_request->priority();
   request.skip_service_worker = true;
   request.do_not_prompt_for_login = true;
@@ -704,6 +766,8 @@
 bool ServiceWorkerFetchDispatcher::MaybeStartNavigationPreloadWithURLLoader(
     const network::ResourceRequest& original_request,
     URLLoaderFactoryGetter* url_loader_factory_getter,
+    scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
+    const WebContentsGetter& web_contents_getter,
     base::OnceClosure on_response) {
   if (resource_type_ != RESOURCE_TYPE_MAIN_FRAME &&
       resource_type_ != RESOURCE_TYPE_SUB_FRAME) {
@@ -716,9 +780,7 @@
     return false;
 
   network::ResourceRequest resource_request(original_request);
-  // Set to SUB_RESOURCE because we shouldn't trigger NavigationResourceThrottle
-  // for the service worker navigation preload request.
-  resource_request.resource_type = RESOURCE_TYPE_SUB_RESOURCE;
+  resource_request.resource_type = RESOURCE_TYPE_NAVIGATION_PRELOAD;
   resource_request.skip_service_worker = true;
   resource_request.do_not_prompt_for_login = true;
 
@@ -730,6 +792,28 @@
       "Service-Worker-Navigation-Preload",
       version_->navigation_preload_state().header);
 
+  // Create the network factory.
+  scoped_refptr<network::SharedURLLoaderFactory> factory;
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
+    // In the network service case, create the factory on the UI thread.
+    network::mojom::URLLoaderFactoryPtr network_factory;
+    auto factory_request = mojo::MakeRequest(&network_factory);
+
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(&CreateNetworkFactoryForNavigationPreloadOnUI,
+                       web_contents_getter, std::move(context_wrapper),
+                       mojo::MakeRequest(&network_factory)));
+    factory = base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
+        std::move(network_factory));
+  } else {
+    // In the non-network-service case, use |url_loader_factory_getter|. Unlike
+    // the network service case, we don't need to go to the UI thread to tell
+    // the embedder about the factory since the request will go to
+    // ResourceDispatcherHost which talks to the embedder then.
+    factory = url_loader_factory_getter->GetNetworkFactory();
+  }
+
   preload_handle_ = blink::mojom::FetchEventPreloadHandle::New();
 
   // Create the DelegatingURLLoaderClient, which becomes the
@@ -741,26 +825,28 @@
       std::move(inner_url_loader_client), std::move(on_response),
       resource_request);
 
-  // Start the network request for the URL using the network loader.
-  // TODO(falken): What to do about routing_id, request_id?
+  // Use NavigationURLLoaderImpl to get a unique request id across
+  // browser-initiated navigations and navigation preloads.
+  int request_id = NavigationURLLoaderImpl::MakeGlobalRequestID().request_id;
+
+  // Start the network request for the URL using the network factory.
+  // TODO(falken): What to do about routing_id.
   network::mojom::URLLoaderClientPtr url_loader_client_to_pass;
   url_loader_client->Bind(&url_loader_client_to_pass);
   network::mojom::URLLoaderPtr url_loader;
-  url_loader_factory_getter->GetNetworkFactory()->CreateLoaderAndStart(
-      mojo::MakeRequest(&url_loader), -1 /* routing_id? */,
-      -1 /* request_id? */, network::mojom::kURLLoadOptionNone,
-      resource_request, std::move(url_loader_client_to_pass),
+
+  factory->CreateLoaderAndStart(
+      mojo::MakeRequest(&url_loader), -1 /* routing_id? */, request_id,
+      network::mojom::kURLLoadOptionNone, resource_request,
+      std::move(url_loader_client_to_pass),
       net::MutableNetworkTrafficAnnotationTag(
           kNavigationPreloadTrafficAnnotation));
 
   preload_handle_->url_loader = url_loader.PassInterface();
 
   DCHECK(!url_loader_assets_);
-  // Unlike the non-S13N code path, we don't own the URLLoaderFactory being used
-  // (it's the generic network factory), so we don't need to pass it to
-  // URLLoaderAssets to keep it alive.
   url_loader_assets_ = base::MakeRefCounted<URLLoaderAssets>(
-      nullptr /* url_loader_factory */, std::move(url_loader_client));
+      std::move(factory), std::move(url_loader_client));
   return true;
 }
 
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.h b/content/browser/service_worker/service_worker_fetch_dispatcher.h
index 63e1fdb..9c169b10 100644
--- a/content/browser/service_worker/service_worker_fetch_dispatcher.h
+++ b/content/browser/service_worker/service_worker_fetch_dispatcher.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_
 
 #include <memory>
+#include <string>
 
 #include "base/callback.h"
 #include "base/macros.h"
@@ -52,6 +53,7 @@
                               blink::mojom::ServiceWorkerStreamHandlePtr,
                               blink::mojom::ServiceWorkerFetchEventTimingPtr,
                               scoped_refptr<ServiceWorkerVersion>)>;
+  using WebContentsGetter = base::RepeatingCallback<WebContents*()>;
 
   ServiceWorkerFetchDispatcher(blink::mojom::FetchAPIRequestPtr request,
                                ResourceType resource_type,
@@ -72,6 +74,8 @@
   bool MaybeStartNavigationPreloadWithURLLoader(
       const network::ResourceRequest& original_request,
       URLLoaderFactoryGetter* url_loader_factory_getter,
+      scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
+      const WebContentsGetter& web_contents_getter,
       base::OnceClosure on_response);
 
   // Dispatches a fetch event to the |version| given in ctor, and fires
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 60e00c11..ca894b5 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -5,6 +5,7 @@
 #include "content/browser/service_worker/service_worker_navigation_loader.h"
 
 #include <sstream>
+#include <string>
 #include <utility>
 
 #include "base/bind.h"
@@ -14,6 +15,8 @@
 #include "base/optional.h"
 #include "base/strings/strcat.h"
 #include "base/trace_event/trace_event.h"
+#include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_version.h"
 #include "content/browser/url_loader_factory_getter.h"
@@ -227,6 +230,14 @@
     return;
   }
 
+  base::WeakPtr<ServiceWorkerContextCore> core = active_worker->context();
+  if (!core) {
+    CommitCompleted(net::ERR_ABORTED, "No service worker context");
+    return;
+  }
+  scoped_refptr<ServiceWorkerContextWrapper> context = core->wrapper();
+  DCHECK(context);
+
   // Dispatch the fetch event.
   fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
       blink::mojom::FetchAPIRequest::From(resource_request_),
@@ -242,6 +253,7 @@
   did_navigation_preload_ =
       fetch_dispatcher_->MaybeStartNavigationPreloadWithURLLoader(
           resource_request_, url_loader_factory_getter_.get(),
+          std::move(context), provider_host_->web_contents_getter(),
           base::DoNothing(/* TODO(crbug/762357): metrics? */));
 
   // Record worker start time here as |fetch_dispatcher_| will start a service
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index 425b53e..9dffb5e6 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -83,103 +83,6 @@
   return response;
 }
 
-// NavigationPreloadLoaderClient mocks the renderer-side URLLoaderClient for the
-// navigation preload network request performed by the browser. In production
-// code, this is ServiceWorkerContextClient::NavigationPreloadRequest,
-// which it forwards the response to FetchEvent#preloadResponse. Here, it
-// simulates passing the response to FetchEvent#respondWith.
-//
-// The navigation preload test is quite involved. The flow of data is:
-// 1. ServiceWorkerNavigationLoader asks ServiceWorkerFetchDispatcher to start
-//    navigation preload.
-// 2. ServiceWorkerFetchDispatcher starts the network request which is mocked
-//    by EmbeddedWorkerTestHelper's default network loader factory. The
-//    response is sent to
-//    ServiceWorkerFetchDispatcher::DelegatingURLLoaderClient.
-// 3. DelegatingURLLoaderClient sends the response to the |preload_handle|
-//    that was passed to Helper::OnFetchEvent().
-// 4. Helper::OnFetchEvent() creates NavigationPreloadLoaderClient, which
-//    receives the response.
-// 5. NavigationPreloadLoaderClient calls OnFetchEvent()'s callbacks
-//    with the response.
-// 6. Like all FetchEvent responses, the response is sent to
-//    ServiceWorkerNavigationLoader::DidDispatchFetchEvent, and the
-//    RequestHandler is returned.
-class NavigationPreloadLoaderClient final
-    : public network::mojom::URLLoaderClient {
- public:
-  NavigationPreloadLoaderClient(
-      blink::mojom::FetchEventPreloadHandlePtr preload_handle,
-      blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
-      blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback)
-      : url_loader_(std::move(preload_handle->url_loader)),
-        binding_(this, std::move(preload_handle->url_loader_client_request)),
-        response_callback_(std::move(response_callback)),
-        finish_callback_(std::move(finish_callback)) {
-    binding_.set_connection_error_handler(
-        base::BindOnce(&NavigationPreloadLoaderClient::OnConnectionError,
-                       base::Unretained(this)));
-  }
-  ~NavigationPreloadLoaderClient() override = default;
-
-  // network::mojom::URLLoaderClient implementation
-  void OnReceiveResponse(
-      const network::ResourceResponseHead& response_head) override {
-    response_head_ = response_head;
-  }
-  void OnStartLoadingResponseBody(
-      mojo::ScopedDataPipeConsumerHandle body) override {
-    body_ = std::move(body);
-    // We could call OnResponseStream() here, but for simplicity, don't do
-    // anything until OnComplete().
-  }
-  void OnComplete(const network::URLLoaderCompletionStatus& status) override {
-    blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
-    auto stream_handle = blink::mojom::ServiceWorkerStreamHandle::New();
-    stream_handle->callback_request = mojo::MakeRequest(&stream_callback);
-    stream_handle->stream = std::move(body_);
-
-    // Simulate passing the navigation preload response to
-    // FetchEvent#respondWith.
-    auto response = blink::mojom::FetchAPIResponse::New();
-    response->url_list =
-        std::vector<GURL>(response_head_.url_list_via_service_worker);
-    response->status_code = response_head_.headers->response_code();
-    response->status_text = response_head_.headers->GetStatusText();
-    response->response_type = response_head_.response_type;
-    response_callback_->OnResponseStream(
-        std::move(response), std::move(stream_handle),
-        blink::mojom::ServiceWorkerFetchEventTiming::New());
-    std::move(finish_callback_)
-        .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
-    stream_callback->OnCompleted();
-    delete this;
-  }
-  void OnReceiveRedirect(
-      const net::RedirectInfo& redirect_info,
-      const network::ResourceResponseHead& response_head) override {}
-  void OnUploadProgress(int64_t current_position,
-                        int64_t total_size,
-                        OnUploadProgressCallback ack_callback) override {}
-  void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {}
-  void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
-
-  void OnConnectionError() { delete this; }
-
- private:
-  network::mojom::URLLoaderPtr url_loader_;
-  mojo::Binding<network::mojom::URLLoaderClient> binding_;
-
-  network::ResourceResponseHead response_head_;
-  mojo::ScopedDataPipeConsumerHandle body_;
-
-  // Callbacks that complete Helper::OnFetchEvent().
-  blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_;
-  blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(NavigationPreloadLoaderClient);
-};
-
 // Simulates a service worker handling fetch events. The response can be
 // customized via RespondWith* functions.
 class FetchEventServiceWorker : public FakeServiceWorker {
@@ -216,13 +119,6 @@
   // Tells this worker to respond to fetch events with an error response.
   void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; }
 
-  // Tells this worker to respond to fetch events with
-  // FetchEvent#preloadResponse. See NavigationPreloadLoaderClient's
-  // documentation for details.
-  void RespondWithNavigationPreloadResponse() {
-    response_mode_ = ResponseMode::kNavigationPreloadResponse;
-  }
-
   // Tells this worker to respond to fetch events with the redirect response.
   void RespondWithRedirectResponse(const GURL& new_url) {
     response_mode_ = ResponseMode::kRedirect;
@@ -326,12 +222,6 @@
         std::move(finish_callback)
             .Run(blink::mojom::ServiceWorkerEventStatus::REJECTED);
         break;
-      case ResponseMode::kNavigationPreloadResponse:
-        // Deletes itself when done.
-        new NavigationPreloadLoaderClient(std::move(params->preload_handle),
-                                          std::move(response_callback),
-                                          std::move(finish_callback));
-        break;
       case ResponseMode::kFailFetchEventDispatch:
         // Simulate failure by stopping the worker before the event finishes.
         // This causes ServiceWorkerVersion::StartRequest() to call its error
@@ -380,7 +270,6 @@
     kStream,
     kFallbackResponse,
     kErrorResponse,
-    kNavigationPreloadResponse,
     kFailFetchEventDispatch,
     kDeferredResponse,
     kEarlyResponse,
@@ -1063,32 +952,6 @@
       0);
 }
 
-// Test responding to the fetch event with the navigation preload response.
-TEST_F(ServiceWorkerNavigationLoaderTest, NavigationPreload) {
-  registration_->EnableNavigationPreload(true);
-  service_worker_->RespondWithNavigationPreloadResponse();
-
-  // Perform the request
-  LoaderResult result = StartRequest(CreateRequest());
-  ASSERT_EQ(LoaderResult::kHandledRequest, result);
-  client_.RunUntilComplete();
-
-  EXPECT_EQ(net::OK, client_.completion_status().error_code);
-  const network::ResourceResponseHead& info = client_.response_head();
-  EXPECT_EQ(200, info.headers->response_code());
-
-  std::unique_ptr<network::ResourceResponseHead> expected_info =
-      CreateResponseInfoFromServiceWorker();
-  expected_info->did_service_worker_navigation_preload = true;
-  ExpectResponseInfo(info, *expected_info);
-
-  std::string response;
-  EXPECT_TRUE(client_.response_body().is_valid());
-  EXPECT_TRUE(
-      mojo::BlockingCopyToString(client_.response_body_release(), &response));
-  EXPECT_EQ("this body came from the network", response);
-}
-
 // Test responding to the fetch event with a redirect response.
 TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) {
   base::HistogramTester histogram_tester;
diff --git a/content/browser/service_worker/service_worker_new_script_loader.cc b/content/browser/service_worker/service_worker_new_script_loader.cc
index 438b723..9a4ebc2 100644
--- a/content/browser/service_worker/service_worker_new_script_loader.cc
+++ b/content/browser/service_worker/service_worker_new_script_loader.cc
@@ -96,7 +96,7 @@
     resource_request.headers.SetHeader("Service-Worker", "script");
   }
 
-  // Bypass the browser cache if needed, e.g., updateViaCache demands it or 24
+  // Validate the browser cache if needed, e.g., updateViaCache demands it or 24
   // hours passed since the last update check that hit network.
   base::TimeDelta time_since_last_check =
       base::Time::Now() - registration->last_update_check();
@@ -104,7 +104,7 @@
           is_main_script, registration->update_via_cache()) ||
       time_since_last_check > kServiceWorkerScriptMaxCacheAge ||
       version_->force_bypass_cache_for_scripts()) {
-    resource_request.load_flags |= net::LOAD_BYPASS_CACHE;
+    resource_request.load_flags |= net::LOAD_VALIDATE_CACHE;
   }
 
   ServiceWorkerStorage* storage = version_->context()->storage();
diff --git a/content/browser/service_worker/service_worker_new_script_loader_unittest.cc b/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
index 1a6ff0d..3b946dd 100644
--- a/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_new_script_loader_unittest.cc
@@ -667,7 +667,7 @@
   EXPECT_EQ(0UL, version_->script_cache_map()->size());
 }
 
-// Tests cache bypassing behavior when updateViaCache is 'all'.
+// Tests cache validation behavior when updateViaCache is 'all'.
 TEST_F(ServiceWorkerNewScriptLoaderTest, UpdateViaCache_All) {
   std::unique_ptr<network::TestURLLoaderClient> client;
   std::unique_ptr<ServiceWorkerNewScriptLoader> loader;
@@ -680,37 +680,37 @@
   options.update_via_cache = blink::mojom::ServiceWorkerUpdateViaCache::kAll;
   SetUpRegistrationWithOptions(kScriptURL, options);
 
-  // Install the main script and imported script. The cache should be bypassed
+  // Install the main script and imported script. The cache should validate
   // since last update time is null.
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   network::ResourceRequest request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   // Promote to active and prepare to update.
   ActivateVersion();
   registration_->set_last_update_check(base::Time::Now());
 
-  // Attempt to update. The requests should not bypass cache since the last
-  // update was recent.
+  // Attempt to update. The requests should not validate the cache since the
+  // last update was recent.
   SetUpVersion(kScriptURL);
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_FALSE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_FALSE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_FALSE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_FALSE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
-  // Set update check to far in the past and repeat. The requests should bypass
-  // cache.
+  // Set update check to far in the past and repeat. The requests should
+  // validate the cache.
   registration_->set_last_update_check(base::Time::Now() -
                                        base::TimeDelta::FromHours(24));
 
@@ -718,15 +718,15 @@
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 }
 
-// Tests cache bypassing behavior when updateViaCache is 'imports'.
+// Tests cache validation behavior when updateViaCache is 'imports'.
 TEST_F(ServiceWorkerNewScriptLoaderTest, UpdateViaCache_Imports) {
   std::unique_ptr<network::TestURLLoaderClient> client;
   std::unique_ptr<ServiceWorkerNewScriptLoader> loader;
@@ -740,36 +740,36 @@
       blink::mojom::ServiceWorkerUpdateViaCache::kImports;
   SetUpRegistrationWithOptions(kScriptURL, options);
 
-  // Install the main script and imported script. The cache should be bypassed
+  // Install the main script and imported script. The cache should be validated
   // since last update time is null.
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   network::ResourceRequest request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   // Promote to active and prepare to update.
   ActivateVersion();
   registration_->set_last_update_check(base::Time::Now());
 
-  // Attempt to update. Only the imported script should bypass cache because
-  // kImports.
+  // Attempt to update. Only the imported script should validate the cache
+  // because kImports.
   SetUpVersion(kScriptURL);
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_FALSE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_FALSE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
-  // Set the time to far in the past and repeat. The requests should bypass
+  // Set the time to far in the past and repeat. The requests should validate
   // cache.
   registration_->set_last_update_check(base::Time::Now() -
                                        base::TimeDelta::FromHours(24));
@@ -778,15 +778,15 @@
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 }
 
-// Tests cache bypassing behavior when updateViaCache is 'none'.
+// Tests cache validation behavior when updateViaCache is 'none'.
 TEST_F(ServiceWorkerNewScriptLoaderTest, UpdateViaCache_None) {
   const GURL kScriptURL(kNormalScriptURL);
   const GURL kImportedScriptURL(kNormalImportedScriptURL);
@@ -799,33 +799,33 @@
   options.update_via_cache = blink::mojom::ServiceWorkerUpdateViaCache::kNone;
   SetUpRegistrationWithOptions(kScriptURL, options);
 
-  // Install the main script and imported script. The cache should be bypassed
+  // Install the main script and imported script. The cache should be validated
   // since kNone (and the last update time is null anyway).
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   network::ResourceRequest request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   // Promote to active and prepare to update.
   ActivateVersion();
   registration_->set_last_update_check(base::Time::Now());
 
-  // Attempt to update. The requests should bypass cache because KNone.
+  // Attempt to update. The requests should validate the cache because KNone.
   SetUpVersion(kScriptURL);
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 }
 
 // Tests respecting ServiceWorkerVersion's |force_bypass_cache_for_scripts|
@@ -843,27 +843,27 @@
   // should win.
   options.update_via_cache = blink::mojom::ServiceWorkerUpdateViaCache::kAll;
   SetUpRegistrationWithOptions(kScriptURL, options);
-  // Also set last_update_time to a recent time, so the 24 hour bypass doesn't
-  // kick in.
+  // Also set last_update_time to a recent time, so the 24 hour validation
+  // doesn't kick in.
   registration_->set_last_update_check(base::Time::Now());
 
   version_->set_force_bypass_cache_for_scripts(true);
 
-  // Install the main script and imported script. The cache should be bypassed.
+  // Install the main script and imported script. The cache should be validated.
   DoRequest(kScriptURL, &client, &loader);
   client->RunUntilComplete();
   network::ResourceRequest request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 
   DoRequest(kImportedScriptURL, &client, &loader);
   client->RunUntilComplete();
   request = mock_url_loader_factory_->last_request();
-  EXPECT_TRUE(request.load_flags & net::LOAD_BYPASS_CACHE);
+  EXPECT_TRUE(request.load_flags & net::LOAD_VALIDATE_CACHE);
 }
 
 // Tests that EmbeddedWorkerInstance's |network_accessed_for_script_| flag is
 // set when the script loader accesses network. This flag is used to enforce the
-// 24 hour cache bypass.
+// 24 hour cache validation.
 TEST_F(ServiceWorkerNewScriptLoaderTest, AccessedNetwork) {
   const GURL kScriptURL(kNormalScriptURL);
   const GURL kImportedScriptURL(kNormalImportedScriptURL);
diff --git a/content/browser/webui/web_ui_data_source_impl.cc b/content/browser/webui/web_ui_data_source_impl.cc
index 82e2de8b..6c52eda 100644
--- a/content/browser/webui/web_ui_data_source_impl.cc
+++ b/content/browser/webui/web_ui_data_source_impl.cc
@@ -267,6 +267,9 @@
   if (base::EndsWith(file_path, ".svg", base::CompareCase::INSENSITIVE_ASCII))
     return "image/svg+xml";
 
+  if (base::EndsWith(file_path, ".jpg", base::CompareCase::INSENSITIVE_ASCII))
+    return "image/jpeg";
+
   return "text/html";
 }
 
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index ff4746c1..1a180aa 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -287,9 +287,6 @@
   WebRuntimeFeatures::EnableNetworkService(
       base::FeatureList::IsEnabled(network::features::kNetworkService));
 
-  if (base::FeatureList::IsEnabled(features::kGamepadVibration))
-    WebRuntimeFeatures::EnableGamepadVibration(true);
-
   if (base::FeatureList::IsEnabled(features::kCompositeOpaqueFixedPosition))
     WebRuntimeFeatures::EnableFeatureFromString("CompositeOpaqueFixedPosition",
                                                 true);
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionClient.java b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionClient.java
index 8c2aeb18..e0254e7 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionClient.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionClient.java
@@ -27,17 +27,17 @@
  */
 @JNINamespace("content")
 public class SmartSelectionClient implements SelectionClient {
-    @IntDef({CLASSIFY, SUGGEST_AND_CLASSIFY})
+    @IntDef({RequestType.CLASSIFY, RequestType.SUGGEST_AND_CLASSIFY})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface RequestType {}
+    private @interface RequestType {
+        // Request to obtain the type (e.g. phone number, e-mail address) and the most
+        // appropriate operation for the selected text.
+        int CLASSIFY = 0;
 
-    // Request to obtain the type (e.g. phone number, e-mail address) and the most
-    // appropriate operation for the selected text.
-    private static final int CLASSIFY = 0;
-
-    // Request to obtain the type (e.g. phone number, e-mail address), the most
-    // appropriate operation for the selected text and a better selection boundaries.
-    private static final int SUGGEST_AND_CLASSIFY = 1;
+        // Request to obtain the type (e.g. phone number, e-mail address), the most
+        // appropriate operation for the selected text and a better selection boundaries.
+        int SUGGEST_AND_CLASSIFY = 1;
+    }
 
     // The maximal number of characters on the left and on the right from the current selection.
     // Used for surrounding text request.
@@ -88,7 +88,8 @@
 
     @Override
     public boolean requestSelectionPopupUpdates(boolean shouldSuggest) {
-        requestSurroundingText(shouldSuggest ? SUGGEST_AND_CLASSIFY : CLASSIFY);
+        requestSurroundingText(
+                shouldSuggest ? RequestType.SUGGEST_AND_CLASSIFY : RequestType.CLASSIFY);
         return true;
     }
 
@@ -139,11 +140,11 @@
         }
 
         switch (callbackData) {
-            case SUGGEST_AND_CLASSIFY:
+            case RequestType.SUGGEST_AND_CLASSIFY:
                 mProvider.sendSuggestAndClassifyRequest(text, start, end, null);
                 break;
 
-            case CLASSIFY:
+            case RequestType.CLASSIFY:
                 mProvider.sendClassifyRequest(text, start, end, null);
                 break;
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionMetricsLogger.java b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionMetricsLogger.java
index 7923917..37dbe5a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionMetricsLogger.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionMetricsLogger.java
@@ -50,10 +50,10 @@
     private SelectionIndicesConverter mConverter;
 
     // ActionType, from SmartSelectionEventTracker.SelectionEvent class.
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({ActionType.OVERTYPE, ActionType.COPY, ActionType.PASTE, ActionType.CUT,
             ActionType.SHARE, ActionType.SMART_SHARE, ActionType.DRAG, ActionType.ABANDON,
             ActionType.OTHER, ActionType.SELECT_ALL, ActionType.RESET})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ActionType {
         /** User typed over the selection. */
         int OVERTYPE = 100;
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
index 2760dd1..83ad2fd5 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SmartSelectionProvider.java
@@ -30,12 +30,12 @@
 public class SmartSelectionProvider {
     private static final String TAG = "SmartSelProvider";
 
-    @IntDef({CLASSIFY, SUGGEST_AND_CLASSIFY})
+    @IntDef({RequestType.CLASSIFY, RequestType.SUGGEST_AND_CLASSIFY})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface RequestType {}
-
-    private static final int CLASSIFY = 0;
-    private static final int SUGGEST_AND_CLASSIFY = 1;
+    private @interface RequestType {
+        int CLASSIFY = 0;
+        int SUGGEST_AND_CLASSIFY = 1;
+    }
 
     private SelectionClient.ResultCallback mResultCallback;
     private WindowAndroid mWindowAndroid;
@@ -60,11 +60,11 @@
 
     public void sendSuggestAndClassifyRequest(
             CharSequence text, int start, int end, Locale[] locales) {
-        sendSmartSelectionRequest(SUGGEST_AND_CLASSIFY, text, start, end, locales);
+        sendSmartSelectionRequest(RequestType.SUGGEST_AND_CLASSIFY, text, start, end, locales);
     }
 
     public void sendClassifyRequest(CharSequence text, int start, int end, Locale[] locales) {
-        sendSmartSelectionRequest(CLASSIFY, text, start, end, locales);
+        sendSmartSelectionRequest(RequestType.CLASSIFY, text, start, end, locales);
     }
 
     public void cancelAllRequests() {
@@ -118,7 +118,7 @@
     @TargetApi(Build.VERSION_CODES.O)
     private class ClassificationTask extends AsyncTask<SelectionClient.Result> {
         private final TextClassifier mTextClassifier;
-        private final int mRequestType;
+        private final @RequestType int mRequestType;
         private final CharSequence mText;
         private final int mOriginalStart;
         private final int mOriginalEnd;
@@ -141,7 +141,7 @@
 
             TextSelection textSelection = null;
 
-            if (mRequestType == SUGGEST_AND_CLASSIFY) {
+            if (mRequestType == RequestType.SUGGEST_AND_CLASSIFY) {
                 textSelection = mTextClassifier.suggestSelection(
                         mText, start, end, makeLocaleList(mLocales));
                 start = Math.max(0, textSelection.getSelectionStartIndex());
diff --git a/content/public/browser/gpu_utils.cc b/content/public/browser/gpu_utils.cc
index 60e2ada..39f045b 100644
--- a/content/public/browser/gpu_utils.cc
+++ b/content/public/browser/gpu_utils.cc
@@ -116,8 +116,7 @@
   gpu_preferences.enable_passthrough_raster_decoder =
       command_line->HasSwitch(switches::kEnablePassthroughRasterDecoder);
 #if defined(OS_WIN)
-  if (gpu_preferences.enable_oop_rasterization)
-    gpu_preferences.enable_passthrough_raster_decoder = true;
+  gpu_preferences.enable_passthrough_raster_decoder = true;
 #endif
 
   gpu_preferences.enable_vulkan =
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 4296c21..0fe852b 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -184,10 +184,6 @@
 const base::Feature kFreezeFramesOnVisibility{
     "FreezeFramesOnVisibility", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables haptic vibration effects on supported gamepads.
-const base::Feature kGamepadVibration{"GamepadVibration",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Puts network quality estimate related Web APIs in the holdback mode. When the
 // holdback is enabled the related Web APIs return network quality estimate
 // set by the experiment (regardless of the actual quality).
@@ -439,8 +435,7 @@
 // Signed Exchange Reporting for distributors
 // https://www.chromestatus.com/features/5687904902840320
 const base::Feature kSignedExchangeReportingForDistributors{
-    "SignedExchangeReportingForDistributors",
-    base::FEATURE_DISABLED_BY_DEFAULT};
+    "SignedExchangeReportingForDistributors", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Origin-Signed HTTP Exchanges (for WebPackage Loading)
 // https://www.chromestatus.com/features/5745285984681984
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 78c91a4..342a639 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -52,7 +52,6 @@
 CONTENT_EXPORT extern const base::Feature
     kFramebustingNeedsSameOriginOrUserGesture;
 CONTENT_EXPORT extern const base::Feature kFreezeFramesOnVisibility;
-CONTENT_EXPORT extern const base::Feature kGamepadVibration;
 CONTENT_EXPORT extern const base::Feature kGuestViewCrossProcessFrames;
 CONTENT_EXPORT extern const base::Feature kHeapCompaction;
 CONTENT_EXPORT extern const base::Feature kHistoryManipulationIntervention;
diff --git a/content/public/common/resource_type.h b/content/public/common/resource_type.h
index b908921..8c87f6f 100644
--- a/content/public/common/resource_type.h
+++ b/content/public/common/resource_type.h
@@ -30,7 +30,9 @@
   RESOURCE_TYPE_SERVICE_WORKER = 15,  // the main resource of a service worker.
   RESOURCE_TYPE_CSP_REPORT = 16,      // a report of Content Security Policy
                                       // violations.
-  RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested.
+  RESOURCE_TYPE_PLUGIN_RESOURCE = 17,  // a resource that a plugin requested.
+  RESOURCE_TYPE_NAVIGATION_PRELOAD =
+      18,  // a service worker navigation preload request.
   RESOURCE_TYPE_LAST_TYPE
 };
 
diff --git a/content/public/common/resource_type.mojom b/content/public/common/resource_type.mojom
index 98211a5..35295cd 100644
--- a/content/public/common/resource_type.mojom
+++ b/content/public/common/resource_type.mojom
@@ -26,5 +26,7 @@
   RESOURCE_TYPE_CSP_REPORT = 16,      // a report of Content Security Policy
                                       // violations.
   RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested.
+  RESOURCE_TYPE_NAVIGATION_PRELOAD =
+      18, // a service worker navigation preload request.
   RESOURCE_TYPE_LAST_TYPE
 };
diff --git a/content/public/common/resource_type_struct_traits.cc b/content/public/common/resource_type_struct_traits.cc
index 5bc012f..414852d 100644
--- a/content/public/common/resource_type_struct_traits.cc
+++ b/content/public/common/resource_type_struct_traits.cc
@@ -47,6 +47,8 @@
       return content::mojom::ResourceType::RESOURCE_TYPE_CSP_REPORT;
     case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
       return content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE;
+    case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
+      return content::mojom::ResourceType::RESOURCE_TYPE_NAVIGATION_PRELOAD;
     case content::RESOURCE_TYPE_LAST_TYPE:
       return content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE;
   }
@@ -114,6 +116,9 @@
     case content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE:
       *output = content::RESOURCE_TYPE_PLUGIN_RESOURCE;
       return true;
+    case content::mojom::ResourceType::RESOURCE_TYPE_NAVIGATION_PRELOAD:
+      *output = content::RESOURCE_TYPE_NAVIGATION_PRELOAD;
+      return true;
     case content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE:
       *output = content::RESOURCE_TYPE_LAST_TYPE;
       return true;
diff --git a/content/renderer/input/widget_input_handler_impl.cc b/content/renderer/input/widget_input_handler_impl.cc
index fbc544cf..6e7a3d1a 100644
--- a/content/renderer/input/widget_input_handler_impl.cc
+++ b/content/renderer/input/widget_input_handler_impl.cc
@@ -47,9 +47,7 @@
     : main_thread_task_runner_(main_thread_task_runner),
       input_handler_manager_(manager),
       input_event_queue_(input_event_queue),
-      render_widget_(render_widget),
-      binding_(this),
-      associated_binding_(this) {}
+      render_widget_(render_widget) {}
 
 WidgetInputHandlerImpl::~WidgetInputHandlerImpl() {}
 
@@ -162,8 +160,22 @@
 
 void WidgetInputHandlerImpl::WaitForInputProcessed(
     WaitForInputProcessedCallback callback) {
-  // TODO(bokan): Implement this to actually ensure input is processed.
-  std::move(callback).Run();
+  DCHECK(!input_processed_ack_);
+
+  // Store so that we can respond even if the renderer is destructed.
+  input_processed_ack_ = std::move(callback);
+
+  input_handler_manager_->WaitForInputProcessed(
+      base::BindOnce(&WidgetInputHandlerImpl::InputWasProcessed,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void WidgetInputHandlerImpl::InputWasProcessed() {
+  // The callback can be be invoked when the renderer is hidden and then again
+  // when it's shown. We can also be called after Release is called so always
+  // check that the callback exists.
+  if (input_processed_ack_)
+    std::move(input_processed_ack_).Run();
 }
 
 void WidgetInputHandlerImpl::AttachSynchronousCompositor(
@@ -184,6 +196,15 @@
 }
 
 void WidgetInputHandlerImpl::Release() {
+  // If the renderer is closed, make sure we ack the outstanding Mojo callback
+  // so that we don't DCHECK and/or leave the browser-side blocked for an ACK
+  // that will never come if the renderer is destroyed before this callback is
+  // invoked. Note, this method will always be called on the Mojo-bound thread
+  // first and then again on the main thread, the callback will always be
+  // called on the Mojo-bound thread though.
+  if (input_processed_ack_)
+    std::move(input_processed_ack_).Run();
+
   if (!main_thread_task_runner_->BelongsToCurrentThread()) {
     // Close the binding on the compositor thread first before telling the main
     // thread to delete this object.
diff --git a/content/renderer/input/widget_input_handler_impl.h b/content/renderer/input/widget_input_handler_impl.h
index 4e9fa4e..f704aed 100644
--- a/content/renderer/input/widget_input_handler_impl.h
+++ b/content/renderer/input/widget_input_handler_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_RENDERER_INPUT_WIDGET_INPUT_HANDLER_IMPL_H_
 #define CONTENT_RENDERER_INPUT_WIDGET_INPUT_HANDLER_IMPL_H_
 
+#include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
 #include "content/common/input/input_handler.mojom.h"
 #include "mojo/public/cpp/bindings/associated_binding.h"
@@ -60,6 +61,7 @@
       mojom::SynchronousCompositorHostAssociatedPtrInfo host,
       mojom::SynchronousCompositorAssociatedRequest compositor_request)
       override;
+  void InputWasProcessed();
 
  private:
   bool ShouldProxyToMainThread() const;
@@ -71,8 +73,15 @@
   scoped_refptr<MainThreadEventQueue> input_event_queue_;
   base::WeakPtr<RenderWidget> render_widget_;
 
-  mojo::Binding<mojom::WidgetInputHandler> binding_;
-  mojo::AssociatedBinding<mojom::WidgetInputHandler> associated_binding_;
+  // This callback is used to respond to the WaitForInputProcessed Mojo
+  // message. We keep it around so that we can respond even if the renderer is
+  // killed before we actually fully process the input.
+  WaitForInputProcessedCallback input_processed_ack_;
+
+  mojo::Binding<mojom::WidgetInputHandler> binding_{this};
+  mojo::AssociatedBinding<mojom::WidgetInputHandler> associated_binding_{this};
+
+  base::WeakPtrFactory<WidgetInputHandlerImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(WidgetInputHandlerImpl);
 };
diff --git a/content/renderer/input/widget_input_handler_manager.cc b/content/renderer/input/widget_input_handler_manager.cc
index 9b251a0..590c97d 100644
--- a/content/renderer/input/widget_input_handler_manager.cc
+++ b/content/renderer/input/widget_input_handler_manager.cc
@@ -369,6 +369,75 @@
   }
 }
 
+void WidgetInputHandlerManager::InvokeInputProcessedCallback() {
+  DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
+
+  // We can call this method even if we didn't request a callback (e.g. when
+  // the renderer becomes hidden).
+  if (!input_processed_callback_)
+    return;
+
+  // The handler's method needs to respond to the mojo message so it needs to
+  // run on the input handling thread. PostTask to the correct task runner.
+  // Even if we're already on the correct thread, we PostTask for symmetry.
+  base::SingleThreadTaskRunner* mojo_bound_task_runner =
+      compositor_task_runner_ ? compositor_task_runner_.get()
+                              : main_thread_task_runner_.get();
+
+  mojo_bound_task_runner->PostTask(FROM_HERE,
+                                   std::move(input_processed_callback_));
+}
+
+void WidgetInputHandlerManager::InputWasProcessed(
+    const gfx::PresentationFeedback&) {
+  InvokeInputProcessedCallback();
+}
+
+static void WaitForInputProcessedFromMain(
+    base::WeakPtr<RenderWidget> render_widget) {
+  // If the widget is destroyed while we're posting to the main thread, the
+  // Mojo message will be acked in WidgetInputHandlerImpl's destructor.
+  if (!render_widget)
+    return;
+
+  WidgetInputHandlerManager* manager =
+      render_widget->widget_input_handler_manager();
+
+  // If the RenderWidget is hidden, we won't produce compositor frames for it
+  // so just ACK the input to prevent blocking the browser indefinitely.
+  if (render_widget->is_hidden()) {
+    manager->InvokeInputProcessedCallback();
+    return;
+  }
+
+  auto redraw_complete_callback = base::BindOnce(
+      &WidgetInputHandlerManager::InputWasProcessed, manager->AsWeakPtr());
+
+  // We consider all observable effects of an input gesture to be processed
+  // when the CompositorFrame caused by that input has been produced, send, and
+  // displayed. RequestPresentation will force a a commit and redraw and
+  // callback when the CompositorFrame has been displayed in the display
+  // service. Some examples of non-trivial effects that require waiting that
+  // long: committing NonFastScrollRegions to the compositor, sending
+  // touch-action rects to the browser, and sending updated surface information
+  // to the display compositor for up-to-date OOPIF hit-testing.
+  render_widget->RequestPresentation(std::move(redraw_complete_callback));
+}
+
+void WidgetInputHandlerManager::WaitForInputProcessed(
+    base::OnceClosure callback) {
+  // Note, this will be called from the mojo-bound thread which could be either
+  // main or compositor.
+  DCHECK(!input_processed_callback_);
+  input_processed_callback_ = std::move(callback);
+
+  // We mustn't touch render_widget_ from the impl thread so post all the setup
+  // to the main thread.
+  main_thread_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&WaitForInputProcessedFromMain, render_widget_));
+}
+
 void WidgetInputHandlerManager::InitOnCompositorThread(
     const base::WeakPtr<cc::InputHandler>& input_handler,
     bool smooth_scroll_enabled,
diff --git a/content/renderer/input/widget_input_handler_manager.h b/content/renderer/input/widget_input_handler_manager.h
index 3c2bdd4..2bb3a53 100644
--- a/content/renderer/input/widget_input_handler_manager.h
+++ b/content/renderer/input/widget_input_handler_manager.h
@@ -21,6 +21,10 @@
 }  // namespace scheduler
 }  // namespace blink
 
+namespace gfx {
+struct PresentationFeedback;
+}  // namespace gfx
+
 namespace content {
 class MainThreadEventQueue;
 class SynchronousCompositorRegistry;
@@ -29,9 +33,10 @@
 // This class maintains the compositor InputHandlerProxy and is
 // responsible for passing input events on the compositor and main threads.
 // The lifecycle of this object matches that of the RenderWidget.
-class CONTENT_EXPORT WidgetInputHandlerManager
+class CONTENT_EXPORT WidgetInputHandlerManager final
     : public base::RefCountedThreadSafe<WidgetInputHandlerManager>,
-      public ui::InputHandlerProxyClient {
+      public ui::InputHandlerProxyClient,
+      public base::SupportsWeakPtr<WidgetInputHandlerManager> {
  public:
   static scoped_refptr<WidgetInputHandlerManager> Create(
       base::WeakPtr<RenderWidget> render_widget,
@@ -85,6 +90,10 @@
   content::SynchronousCompositorRegistry* GetSynchronousCompositorRegistry();
 #endif
 
+  void InvokeInputProcessedCallback();
+  void InputWasProcessed(const gfx::PresentationFeedback& feedback);
+  void WaitForInputProcessed(base::OnceClosure callback);
+
  protected:
   friend class base::RefCountedThreadSafe<WidgetInputHandlerManager>;
   ~WidgetInputHandlerManager() override;
@@ -148,6 +157,11 @@
 
   base::Optional<cc::TouchAction> white_listed_touch_action_;
 
+  // Callback used to respond to the WaitForInputProcessed Mojo message. This
+  // callback is set from and must be invoked from the Mojo-bound thread, it
+  // will call into the WidgetInputHandlerImpl.
+  base::OnceClosure input_processed_callback_;
+
 #if defined(OS_ANDROID)
   std::unique_ptr<SynchronousCompositorProxyRegistry>
       synchronous_compositor_registry_;
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 3fb4cda..7c243ad 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -957,10 +957,19 @@
 }
 
 void RenderWidget::OnForceRedraw(int snapshot_id) {
+  RequestPresentation(base::BindOnce(&RenderWidget::DidPresentForceDrawFrame,
+                                     weak_ptr_factory_.GetWeakPtr(),
+                                     snapshot_id));
+}
+
+void RenderWidget::RequestPresentation(PresentationTimeCallback callback) {
   layer_tree_view_->layer_tree_host()->RequestPresentationTimeForNextFrame(
-      base::BindOnce(&RenderWidget::DidPresentForceDrawFrame,
-                     weak_ptr_factory_.GetWeakPtr(), snapshot_id));
+      std::move(callback));
   layer_tree_view_->SetNeedsForcedRedraw();
+
+  // Need this since single thread mode doesn't have a scheduler so the above
+  // call won't cause us to generate a new frame.
+  ScheduleAnimation();
 }
 
 void RenderWidget::DidPresentForceDrawFrame(
@@ -2483,6 +2492,11 @@
   if (render_widget_scheduling_state_)
     render_widget_scheduling_state_->SetHidden(hidden);
 
+  // If the renderer was hidden, resolve any pending synthetic gestures so they
+  // aren't blocked waiting for a compositor frame to be generated.
+  if (is_hidden_)
+    widget_input_handler_manager_->InvokeInputProcessedCallback();
+
   StartStopCompositor();
 }
 
@@ -2936,6 +2950,19 @@
   settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(300);
   settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300);
 
+  if (cmd.HasSwitch(cc::switches::kCCScrollAnimationDurationForTesting)) {
+    const int kMinScrollAnimationDuration = 0;
+    const int kMaxScrollAnimationDuration = INT_MAX;
+    int duration;
+    if (switch_value_as_int(cmd,
+                            cc::switches::kCCScrollAnimationDurationForTesting,
+                            kMinScrollAnimationDuration,
+                            kMaxScrollAnimationDuration, &duration)) {
+      settings.scroll_animation_duration_for_testing =
+          base::TimeDelta::FromSeconds(duration);
+    }
+  }
+
 #if defined(OS_ANDROID)
   bool using_synchronous_compositor =
       compositor_deps->UsingSynchronousCompositing();
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 35e17e7..a7dcbd2a 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -93,6 +93,7 @@
 
 namespace gfx {
 class ColorSpace;
+struct PresentationFeedback;
 class Range;
 }
 
@@ -602,6 +603,12 @@
   // TODO(ajwong): This should be moved into RenderView.
   void UpdateWebViewWithDeviceScaleFactor();
 
+  // Forces a redraw and invokes the callback once the frame's been displayed
+  // to the user.
+  using PresentationTimeCallback =
+      base::OnceCallback<void(const gfx::PresentationFeedback&)>;
+  void RequestPresentation(PresentationTimeCallback callback);
+
   // RenderWidget IPC message handler that can be overridden by subclasses.
   virtual void OnSynchronizeVisualProperties(const VisualProperties& params);
 
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index 7996345..b540b8f5 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -52,6 +52,7 @@
 #include "content/shell/test_runner/pixel_dump.h"
 #include "content/shell/test_runner/web_test_interfaces.h"
 #include "content/shell/test_runner/web_test_runner.h"
+#include "content/shell/test_runner/web_widget_test_proxy.h"
 #include "media/base/audio_capturer_source.h"
 #include "media/base/audio_parameters.h"
 #include "media/capture/video_capturer_source.h"
@@ -722,19 +723,22 @@
   prefs_.Reset();
   waiting_for_reset_ = false;
 
+  WebFrame* main_frame = render_view()->GetWebView()->MainFrame();
+
   render_view()->ClearEditCommands();
   if (for_new_test) {
-    if (render_view()->GetWebView()->MainFrame()->IsWebLocalFrame())
-      render_view()->GetWebView()->MainFrame()->ToWebLocalFrame()->SetName(
-          WebString());
-    render_view()->GetWebView()->MainFrame()->ClearOpener();
+    if (main_frame->IsWebLocalFrame()) {
+      WebLocalFrame* local_main_frame = main_frame->ToWebLocalFrame();
+      local_main_frame->SetName(WebString());
+      GetWebWidgetTestProxy(local_main_frame)->EndSyntheticGestures();
+    }
+    main_frame->ClearOpener();
   }
 
   // Resetting the internals object also overrides the WebPreferences, so we
   // have to sync them to WebKit again.
-  if (render_view()->GetWebView()->MainFrame()->IsWebLocalFrame()) {
-    WebTestingSupport::ResetInternalsObject(
-        render_view()->GetWebView()->MainFrame()->ToWebLocalFrame());
+  if (main_frame->IsWebLocalFrame()) {
+    WebTestingSupport::ResetInternalsObject(main_frame->ToWebLocalFrame());
     render_view()->SetWebkitPreferences(render_view()->GetWebkitPreferences());
   }
 }
diff --git a/content/shell/test_runner/web_widget_test_proxy.cc b/content/shell/test_runner/web_widget_test_proxy.cc
index e3961a1d..d2774f9f 100644
--- a/content/shell/test_runner/web_widget_test_proxy.cc
+++ b/content/shell/test_runner/web_widget_test_proxy.cc
@@ -5,6 +5,7 @@
 #include "content/shell/test_runner/web_widget_test_proxy.h"
 
 #include "content/renderer/compositor/layer_tree_view.h"
+#include "content/renderer/input/widget_input_handler_manager.h"
 #include "content/shell/test_runner/test_interfaces.h"
 #include "content/shell/test_runner/test_runner.h"
 #include "content/shell/test_runner/test_runner_for_specific_view.h"
@@ -103,6 +104,10 @@
   event_sender_.Install(frame);
 }
 
+void WebWidgetTestProxy::EndSyntheticGestures() {
+  widget_input_handler_manager()->InvokeInputProcessedCallback();
+}
+
 WebWidgetTestProxy::~WebWidgetTestProxy() = default;
 
 TestRunnerForSpecificView* WebWidgetTestProxy::GetViewTestRunner() {
diff --git a/content/shell/test_runner/web_widget_test_proxy.h b/content/shell/test_runner/web_widget_test_proxy.h
index e388422..df4edea 100644
--- a/content/shell/test_runner/web_widget_test_proxy.h
+++ b/content/shell/test_runner/web_widget_test_proxy.h
@@ -80,6 +80,8 @@
   void Reset();
   void BindTo(blink::WebLocalFrame* frame);
 
+  void EndSyntheticGestures();
+
  private:
   // RenderWidget does not have a public destructor.
   ~WebWidgetTestProxy() override;
diff --git a/device/fido/mac/touch_id_context.mm b/device/fido/mac/touch_id_context.mm
index 25cef5f..d96ddbc 100644
--- a/device/fido/mac/touch_id_context.mm
+++ b/device/fido/mac/touch_id_context.mm
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
+#include "base/mac/sdk_forward_declarations.h"
 #include "base/memory/ptr_util.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/sys_string_conversions.h"
@@ -49,9 +50,12 @@
 // static
 bool TouchIdContext::TouchIdAvailableImpl() {
   base::scoped_nsobject<LAContext> context([[LAContext alloc] init]);
-  return
+  bool available =
       [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
                            error:nil];
+  if (@available(macOS 10.13.2, *))
+    return available && [context biometryType] == LABiometryTypeTouchID;
+  return available;
 }
 
 // static
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index bafda5f..532415d 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -111,7 +111,7 @@
         "openvr/openvr_render_loop.h",
         "openvr/openvr_type_converters.cc",
         "openvr/openvr_type_converters.h",
-        "openvr/test/test_hook.h",
+        "test/test_hook.h",
       ]
     }
 
@@ -223,7 +223,7 @@
       "openvr/test/fake_openvr_impl_api.cc",
       "openvr/test/test_helper.cc",
       "openvr/test/test_helper.h",
-      "openvr/test/test_hook.h",
+      "test/test_hook.h",
     ]
 
     libs = [
diff --git a/device/vr/openvr/openvr_api_wrapper.cc b/device/vr/openvr/openvr_api_wrapper.cc
index 61de317..5f5d463 100644
--- a/device/vr/openvr/openvr_api_wrapper.cc
+++ b/device/vr/openvr/openvr_api_wrapper.cc
@@ -6,7 +6,7 @@
 
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "device/vr/openvr/test/test_hook.h"
+#include "device/vr/test/test_hook.h"
 
 namespace device {
 
@@ -29,7 +29,7 @@
   return system_;
 }
 
-void OpenVRWrapper::SetTestHook(OpenVRTestHook* hook) {
+void OpenVRWrapper::SetTestHook(VRTestHook* hook) {
   // This may be called from any thread - tests are responsible for
   // maintaining thread safety, typically by not changing the test hook
   // while presenting.
@@ -93,7 +93,7 @@
   any_initialized_ = false;
 }
 
-OpenVRTestHook* OpenVRWrapper::test_hook_ = nullptr;
+VRTestHook* OpenVRWrapper::test_hook_ = nullptr;
 bool OpenVRWrapper::any_initialized_ = false;
 TestHookRegistration* OpenVRWrapper::test_hook_registration_ = nullptr;
 
diff --git a/device/vr/openvr/openvr_api_wrapper.h b/device/vr/openvr/openvr_api_wrapper.h
index 7ee97958..ba80852 100644
--- a/device/vr/openvr/openvr_api_wrapper.h
+++ b/device/vr/openvr/openvr_api_wrapper.h
@@ -14,7 +14,7 @@
 }
 
 namespace device {
-class OpenVRTestHook;
+class VRTestHook;
 class TestHookRegistration;
 
 class OpenVRWrapper {
@@ -29,7 +29,7 @@
   vr::IVRCompositor* GetCompositor();
   vr::IVRSystem* GetSystem();
 
-  static void DEVICE_VR_EXPORT SetTestHook(OpenVRTestHook* hook);
+  static void DEVICE_VR_EXPORT SetTestHook(VRTestHook* hook);
 
  private:
   bool Initialize(bool for_rendering);
@@ -41,7 +41,7 @@
   bool initialized_ = false;
 
   static TestHookRegistration* test_hook_registration_;
-  static OpenVRTestHook* test_hook_;
+  static VRTestHook* test_hook_;
   static bool any_initialized_;
 };
 
diff --git a/device/vr/openvr/openvr_device_provider.cc b/device/vr/openvr/openvr_device_provider.cc
index 3ea0fec61..b9c969f 100644
--- a/device/vr/openvr/openvr_device_provider.cc
+++ b/device/vr/openvr/openvr_device_provider.cc
@@ -9,7 +9,7 @@
 #include "device/vr/isolated_gamepad_data_fetcher.h"
 #include "device/vr/openvr/openvr_api_wrapper.h"
 #include "device/vr/openvr/openvr_device.h"
-#include "device/vr/openvr/test/test_hook.h"
+#include "device/vr/test/test_hook.h"
 #include "third_party/openvr/src/headers/openvr.h"
 
 namespace device {
@@ -52,7 +52,7 @@
   std::move(initialization_complete).Run();
 }
 
-void OpenVRDeviceProvider::SetTestHook(OpenVRTestHook* test_hook) {
+void OpenVRDeviceProvider::SetTestHook(VRTestHook* test_hook) {
   OpenVRWrapper::SetTestHook(test_hook);
 }
 
diff --git a/device/vr/openvr/openvr_device_provider.h b/device/vr/openvr/openvr_device_provider.h
index ed9f4285..2678482 100644
--- a/device/vr/openvr/openvr_device_provider.h
+++ b/device/vr/openvr/openvr_device_provider.h
@@ -15,7 +15,7 @@
 namespace device {
 
 class OpenVRDevice;
-class OpenVRTestHook;
+class VRTestHook;
 
 class DEVICE_VR_EXPORT OpenVRDeviceProvider : public VRDeviceProvider {
  public:
@@ -34,7 +34,7 @@
 
   static void RecordRuntimeAvailability();
 
-  static void SetTestHook(OpenVRTestHook*);
+  static void SetTestHook(VRTestHook*);
 
  private:
   void CreateDevice();
diff --git a/device/vr/openvr/test/fake_openvr_impl_api.cc b/device/vr/openvr/test/fake_openvr_impl_api.cc
index eac0b596..6f223f8 100644
--- a/device/vr/openvr/test/fake_openvr_impl_api.cc
+++ b/device/vr/openvr/test/fake_openvr_impl_api.cc
@@ -11,7 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "device/vr/openvr/test/test_helper.h"
-#include "device/vr/openvr/test/test_hook.h"
+#include "device/vr/test/test_hook.h"
 #include "third_party/openvr/src/headers/openvr.h"
 #include "third_party/openvr/src/src/ivrclientcore.h"
 
diff --git a/device/vr/openvr/test/test_helper.cc b/device/vr/openvr/test/test_helper.cc
index fb54414..5d3f3ca2 100644
--- a/device/vr/openvr/test/test_helper.cc
+++ b/device/vr/openvr/test/test_helper.cc
@@ -7,7 +7,7 @@
 #include "base/logging.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_restrictions.h"
-#include "device/vr/openvr/test/test_hook.h"
+#include "device/vr/test/test_hook.h"
 #include "third_party/openvr/src/headers/openvr.h"
 #include "third_party/openvr/src/src/ivrclientcore.h"
 
@@ -283,7 +283,7 @@
   return false;
 }
 
-void TestHelper::SetTestHook(device::OpenVRTestHook* hook) {
+void TestHelper::SetTestHook(device::VRTestHook* hook) {
   base::AutoLock auto_lock(lock_);
   test_hook_ = hook;
 }
diff --git a/device/vr/openvr/test/test_helper.h b/device/vr/openvr/test/test_helper.h
index a772691..0cad4de 100644
--- a/device/vr/openvr/test/test_helper.h
+++ b/device/vr/openvr/test/test_helper.h
@@ -7,7 +7,7 @@
 
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
-#include "device/vr/openvr/test/test_hook.h"
+#include "device/vr/test/test_hook.h"
 #include "third_party/openvr/src/headers/openvr.h"
 
 class ID3D11Texture2D;
@@ -48,10 +48,10 @@
   void DetachFromCurrentThread();
 
   // TestHookRegistration
-  void SetTestHook(device::OpenVRTestHook* hook) final;
+  void SetTestHook(device::VRTestHook* hook) final;
 
  private:
-  device::OpenVRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
+  device::VRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
   base::Lock lock_;
 };
 
diff --git a/device/vr/openvr/test/test_hook.h b/device/vr/test/test_hook.h
similarity index 91%
rename from device/vr/openvr/test/test_hook.h
rename to device/vr/test/test_hook.h
index 3e6914ee..b6e6c23d 100644
--- a/device/vr/openvr/test/test_hook.h
+++ b/device/vr/test/test_hook.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 DEVICE_VR_OPENVR_TEST_TEST_HOOK_H_
-#define DEVICE_VR_OPENVR_TEST_TEST_HOOK_H_
+#ifndef DEVICE_VR_TEST_TEST_HOOK_H_
+#define DEVICE_VR_TEST_TEST_HOOK_H_
 
 #include <cstdint>
 
@@ -80,8 +80,8 @@
   bool is_valid = false;
 };
 
-// Tests may implement this, and register it to control behavior of OpenVR.
-class OpenVRTestHook {
+// Tests may implement this, and register it to control behavior of VR runtime.
+class VRTestHook {
  public:
   virtual void OnFrameSubmitted(SubmittedFrameData frame_data) = 0;
   virtual DeviceConfig WaitGetDeviceConfig() = 0;
@@ -98,9 +98,9 @@
 
 class TestHookRegistration {
  public:
-  virtual void SetTestHook(OpenVRTestHook*) = 0;
+  virtual void SetTestHook(VRTestHook*) = 0;
 };
 
 }  // namespace device
 
-#endif  // DEVICE_VR_OPENVR_TEST_TEST_HOOK_H_
\ No newline at end of file
+#endif  // DEVICE_VR_TEST_TEST_HOOK_H_
\ No newline at end of file
diff --git a/extensions/browser/api/declarative_net_request/ruleset_manager.cc b/extensions/browser/api/declarative_net_request/ruleset_manager.cc
index 4b607e9..f54f621 100644
--- a/extensions/browser/api/declarative_net_request/ruleset_manager.cc
+++ b/extensions/browser/api/declarative_net_request/ruleset_manager.cc
@@ -54,6 +54,7 @@
     case content::RESOURCE_TYPE_LAST_TYPE:
     case content::RESOURCE_TYPE_PREFETCH:
     case content::RESOURCE_TYPE_SUB_RESOURCE:
+    case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
       return flat_rule::ElementType_OTHER;
     case content::RESOURCE_TYPE_MAIN_FRAME:
       return flat_rule::ElementType_MAIN_FRAME;
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc
index d935eeab..2ee53ba 100644
--- a/extensions/browser/api/hid/hid_device_manager.cc
+++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -57,7 +57,9 @@
     api_collection.usage_page = collection->usage->usage_page;
     api_collection.usage = collection->usage->usage;
 
-    api_collection.report_ids = collection->report_ids;
+    api_collection.report_ids.insert(api_collection.report_ids.begin(),
+                                     collection->report_ids.begin(),
+                                     collection->report_ids.end());
 
     output->collections.push_back(std::move(api_collection));
   }
diff --git a/extensions/browser/api/web_request/web_request_info.cc b/extensions/browser/api/web_request/web_request_info.cc
index a51cbc3..d3ec4efa 100644
--- a/extensions/browser/api/web_request/web_request_info.cc
+++ b/extensions/browser/api/web_request/web_request_info.cc
@@ -276,6 +276,7 @@
       render_process_id = url_loader->GetProcessId();
       frame_id = url_loader->GetRenderFrameId();
     }
+    type = static_cast<content::ResourceType>(url_loader->GetResourceType());
   } else {
     // There may be basic process and frame info associated with the request
     // even when |info| is null. Attempt to grab it as a last ditch effort. If
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc
index 9d6b0dda..ae4b7e2 100644
--- a/extensions/browser/api/web_request/web_request_permissions.cc
+++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -285,11 +285,14 @@
 
   if (is_request_from_browser) {
     // Hide all non-navigation requests made by the browser. crbug.com/884932.
-    if (!request.is_browser_side_navigation)
+    if (!request.is_browser_side_navigation &&
+        request.type != content::RESOURCE_TYPE_NAVIGATION_PRELOAD) {
       return true;
+    }
 
     DCHECK(request.type == content::RESOURCE_TYPE_MAIN_FRAME ||
-           request.type == content::RESOURCE_TYPE_SUB_FRAME);
+           request.type == content::RESOURCE_TYPE_SUB_FRAME ||
+           request.type == content::RESOURCE_TYPE_NAVIGATION_PRELOAD);
 
     // Hide sub-frame requests to clientsX.google.com.
     // TODO(crbug.com/890006): Determine if the code here can be cleaned up
diff --git a/extensions/browser/api/web_request/web_request_resource_type.cc b/extensions/browser/api/web_request/web_request_resource_type.cc
index 0c05a2d..b0da58b 100644
--- a/extensions/browser/api/web_request/web_request_resource_type.cc
+++ b/extensions/browser/api/web_request/web_request_resource_type.cc
@@ -78,6 +78,8 @@
       return WebRequestResourceType::CSP_REPORT;
     case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
       return WebRequestResourceType::OBJECT;
+    case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
+      return WebRequestResourceType::OTHER;
     case content::RESOURCE_TYPE_LAST_TYPE:
       return WebRequestResourceType::OTHER;
   }
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 5121932a..8a09fa8 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1383,6 +1383,7 @@
   DECLARATIVENETREQUEST_GETDYNAMICRULES = 1320,
   AUTOTESTPRIVATE_GETARCSTATE = 1321,
   AUTOTESTPRIVATE_ISTABLETMODEENABLED = 1322,
+  AUTOTESTPRIVATE_SETTABLETMODEENABLED = 1323,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc
index 7ad54d7..2de5877f 100644
--- a/extensions/browser/url_loader_factory_manager.cc
+++ b/extensions/browser/url_loader_factory_manager.cc
@@ -62,7 +62,9 @@
 const char* kHardcodedPartOfCorbAllowlist[] = {
     "0149C10F1124F1ED6ACAD85C45E87A76A9DDC667",
     "072D729E856B1F2C9894AEEC3A5DF65E519D6BEE",
+    "07333481B7B8D7F57A9BA64FB98CF86EA87455FC",
     "0EAEA2FDEE025D95B3ABB37014EFF5A98AC4BEAE",
+    "109A37B889E7C8AEA7B0103559C3EB6AF73B7832",
     "16A81AEA09A67B03F7AEA5B957D24A4095E764BE",
     "177508B365CBF1610CC2B53707749D79272F2F0B",
     "1AB9CC404876117F49135E67BAD813F935AAE9BA",
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index e05daa3..e33726d7 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -893,7 +893,9 @@
   // use the old web APIs.
   // After completion of the migration, we should remove this.
   // See crbug.com/924031 for detail.
-  if (extension_id == extension_misc::kPdfExtensionId) {
+  if (extension_id == extension_misc::kPdfExtensionId ||
+      // chrome/common/extensions/extension_constants.h::kZipArchiverExtensionId
+      extension_id == "dmboannefpncccogfdikhmhpmdnddgoe") {
     blink::WebRuntimeFeatures::EnableShadowDOMV0(true);
     blink::WebRuntimeFeatures::EnableCustomElementsV0(true);
     blink::WebRuntimeFeatures::EnableHTMLImports(true);
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index 7038a10b5..f830fd1 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -3795,6 +3795,9 @@
     name: "buildbot/tryserver.chromium.android/android_archive_rel_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_archive_rel_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-binary-size"
   }
   builders {
@@ -3804,6 +3807,9 @@
     name: "buildbot/tryserver.chromium.android/android_cfi_rel_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_cfi_rel_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_clang_dbg_recipe"
   }
   builders {
@@ -3816,9 +3822,15 @@
     name: "buildbot/tryserver.chromium.android/android_compile_x64_dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_compile_x64_dbg"
+  }
+  builders {
     name: "buildbot/tryserver.chromium.android/android_compile_x86_dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_compile_x86_dbg"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_cronet"
   }
   builders {
@@ -3828,6 +3840,9 @@
     name: "buildbot/tryserver.chromium.android/android_mojo"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_mojo"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-marshmallow-arm64-rel"
   }
   builders {
@@ -3843,6 +3858,9 @@
     name: "buildbot/tryserver.chromium.android/linux_android_dbg_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux_android_dbg_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-kitkat-arm-rel"
   }
   builders {
@@ -4362,6 +4380,9 @@
     name: "buildbucket/luci.chromium.try/android-oreo-arm64-cts-networkservice-dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_archive_rel_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_arm64_dbg_recipe"
   }
   builders {
@@ -4383,9 +4404,21 @@
     name: "buildbucket/luci.chromium.try/android_angle_vk64_rel_ng"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_cfi_rel_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_compile_dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android_compile_x64_dbg"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/android_compile_x86_dbg"
+  }
+  builders {
+    name: "buildbucket/luci.chromium.try/android_mojo"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android_n5x_swarming_dbg"
   }
   builders {
@@ -4482,6 +4515,9 @@
     name: "buildbucket/luci.chromium.try/linux-ozone-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/linux_android_dbg_ng"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/linux_angle_dbg_ng"
   }
   builders {
diff --git a/ios/chrome/browser/app_launcher/OWNERS b/ios/chrome/browser/app_launcher/OWNERS
new file mode 100644
index 0000000..02777d02
--- /dev/null
+++ b/ios/chrome/browser/app_launcher/OWNERS
@@ -0,0 +1,5 @@
+mrefaat@chromium.org
+pkl@chromium.org
+
+# TEAM: ios-directory-owners@chromium.org
+# OS: iOS
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index fbc84db..164120786 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -750,9 +750,6 @@
 @end
 
 @implementation BrowserViewController
-// DialogPresenterDelegate property
-@synthesize dialogPresenterDelegateIsPresenting =
-    _dialogPresenterDelegateIsPresenting;
 
 #pragma mark - Object lifecycle
 
@@ -1779,8 +1776,6 @@
                             completion:^{
                               BrowserViewController* strongSelf = weakSelf;
                               strongSelf.dismissingModal = NO;
-                              strongSelf.dialogPresenterDelegateIsPresenting =
-                                  NO;
                               if (completion)
                                 completion();
                               [strongSelf.dialogPresenter tryToPresent];
@@ -1837,7 +1832,6 @@
     }
   }
 
-  self.dialogPresenterDelegateIsPresenting = YES;
   if ([self.sideSwipeController inSwipe]) {
     [self.sideSwipeController resetContentView];
   }
@@ -3705,6 +3699,16 @@
   }
 }
 
+- (BOOL)shouldDialogPresenterPresentDialog:(DialogPresenter*)presenter {
+  if (self.presentedViewController)
+    return NO;
+  for (UIViewController* childViewController in self.childViewControllers) {
+    if (childViewController.presentedViewController)
+      return NO;
+  }
+  return YES;
+}
+
 #pragma mark - ToolbarHeightProviderForFullscreen
 
 - (CGFloat)collapsedTopToolbarHeight {
@@ -4756,7 +4760,6 @@
 }
 
 - (void)activityServiceDidEndPresenting {
-  self.dialogPresenterDelegateIsPresenting = NO;
   [self.dialogPresenter tryToPresent];
 }
 
diff --git a/ios/chrome/browser/ui/dialogs/dialog_presenter.h b/ios/chrome/browser/ui/dialogs/dialog_presenter.h
index a775823..5c702784 100644
--- a/ios/chrome/browser/ui/dialogs/dialog_presenter.h
+++ b/ios/chrome/browser/ui/dialogs/dialog_presenter.h
@@ -96,8 +96,8 @@
 - (void)dialogPresenter:(DialogPresenter*)presenter
     willShowDialogForWebState:(web::WebState*)webState;
 
-// Whether the delegate is presenting another View Controller.
-@property(nonatomic, assign) BOOL dialogPresenterDelegateIsPresenting;
+// Whether |presenter| should present a dialog.
+- (BOOL)shouldDialogPresenterPresentDialog:(DialogPresenter*)presenter;
 
 @end
 
diff --git a/ios/chrome/browser/ui/dialogs/dialog_presenter.mm b/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
index a60d95b5..b37aee5 100644
--- a/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
+++ b/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
@@ -366,11 +366,10 @@
   // displayed.
   if (self.blockingConfirmationCoordinator)
     return;
-  // The active TabModel can't be changed while a JavaScript dialog is shown.
-  DCHECK(!self.showingDialog);
-  if (_active && !_queuedWebStates.empty() &&
-      !self.delegate.dialogPresenterDelegateIsPresenting)
+  if (!self.showingDialog && _active && !_queuedWebStates.empty() &&
+      [self.delegate shouldDialogPresenterPresentDialog:self]) {
     [self showNextDialog];
+  }
 }
 
 + (NSString*)localizedTitleForJavaScriptAlertFromPage:(const GURL&)pageURL
@@ -398,8 +397,9 @@
   _dialogCoordinatorsForWebStates[webState] = coordinator;
 
   if (self.active && !self.showingDialog &&
-      !self.delegate.dialogPresenterDelegateIsPresenting)
+      [self.delegate shouldDialogPresenterPresentDialog:self]) {
     [self showNextDialog];
+  }
 }
 
 - (void)showNextDialog {
@@ -425,8 +425,9 @@
   self.presentedDialogCoordinator = nil;
   self.blockingConfirmationCoordinator = nil;
   if (!_queuedWebStates.empty() &&
-      !self.delegate.dialogPresenterDelegateIsPresenting)
+      [self.delegate shouldDialogPresenterPresentDialog:self]) {
     [self showNextDialog];
+  }
 }
 
 - (void)setUpAlertCoordinator:(AlertCoordinator*)alertCoordinator
diff --git a/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm b/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
index 51906f8..1177e66 100644
--- a/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
+++ b/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
@@ -50,11 +50,18 @@
 }
 // The web states for the dialogs that have been presented.
 @property(nonatomic, readonly) std::vector<web::WebState*> presentedWebStates;
+// Whether the dialog should be allowed to present a dialog.
+@property(nonatomic, assign) BOOL shouldAllowDialogPresentation;
 @end
 
 @implementation TestDialogPresenterDelegate
-@synthesize dialogPresenterDelegateIsPresenting =
-    _dialogPresenterDelegateIsPresenting;
+
+- (instancetype)init {
+  if (self = [super init]) {
+    _shouldAllowDialogPresentation = YES;
+  }
+  return self;
+}
 
 - (std::vector<web::WebState*>)presentedWebStates {
   return _presentedWebStates;
@@ -65,6 +72,10 @@
   _presentedWebStates.push_back(webState);
 }
 
+- (BOOL)shouldDialogPresenterPresentDialog:(DialogPresenter*)presenter {
+  return self.shouldAllowDialogPresentation;
+}
+
 @end
 
 class DialogPresenterTest : public PlatformTest {
@@ -215,7 +226,7 @@
 TEST_F(DialogPresenterTest, DelegatePresenting) {
   // Tests that the dialog is not shown if the delegate is presenting.
   DialogPresenterTestWebState webState1;
-  delegate().dialogPresenterDelegateIsPresenting = YES;
+  delegate().shouldAllowDialogPresentation = NO;
   [presenter() runJavaScriptAlertPanelWithMessage:@""
                                        requestURL:GURL()
                                          webState:&webState1
@@ -225,7 +236,7 @@
   EXPECT_EQ(0U, delegate().presentedWebStates.size());
 
   // The delegate is not presenting anymore, the dialog is not shown yet.
-  delegate().dialogPresenterDelegateIsPresenting = NO;
+  delegate().shouldAllowDialogPresentation = YES;
   EXPECT_EQ(0U, delegate().presentedWebStates.size());
 
   // Notify the presenter that it can present.
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index ac168b9..52082de 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -120,6 +120,8 @@
     "associated_interface_request.h",
     "binding.h",
     "binding_set.h",
+    "call_internal.cc",
+    "call_internal.h",
     "callback_helpers.h",
     "connection_error_callback.h",
     "connector.h",
@@ -162,10 +164,14 @@
     "lib/task_runner_helper.cc",
     "lib/task_runner_helper.h",
     "native_enum.h",
+    "pending_receiver.h",
+    "pending_remote.h",
     "pipe_control_message_handler.h",
     "pipe_control_message_handler_delegate.h",
     "pipe_control_message_proxy.h",
     "raw_ptr_impl_ref_traits.h",
+    "receiver.h",
+    "remote.h",
     "sequence_local_sync_event_watcher.h",
     "strong_associated_binding.h",
     "strong_binding.h",
diff --git a/mojo/public/cpp/bindings/call_internal.cc b/mojo/public/cpp/bindings/call_internal.cc
new file mode 100644
index 0000000..8e4e57e
--- /dev/null
+++ b/mojo/public/cpp/bindings/call_internal.cc
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/cpp/bindings/call_internal.h"
+
+namespace mojo {
+namespace internal {
+
+CallProxyWrapperBase::CallProxyWrapperBase(void* proxy) : proxy_(proxy) {}
+
+CallProxyWrapperBase::CallProxyWrapperBase(CallProxyWrapperBase&& other)
+    : proxy_(other.proxy_) {}
+
+CallProxyWrapperBase::~CallProxyWrapperBase() = default;
+
+}  // namespace internal
+}  // namespace mojo
diff --git a/mojo/public/cpp/bindings/call_internal.h b/mojo/public/cpp/bindings/call_internal.h
new file mode 100644
index 0000000..ddefd8e
--- /dev/null
+++ b/mojo/public/cpp/bindings/call_internal.h
@@ -0,0 +1,48 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_CALL_INTERNAL_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_CALL_INTERNAL_H_
+
+#include <utility>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+
+namespace mojo {
+namespace internal {
+
+// Base class for temporary objects returned by |Remote<T>::rpc()|. This
+// provides pass-through access to the passed calling proxy.
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS) CallProxyWrapperBase {
+ public:
+  explicit CallProxyWrapperBase(void* proxy);
+  CallProxyWrapperBase(CallProxyWrapperBase&& other);
+  ~CallProxyWrapperBase();
+
+  void* proxy() const { return proxy_; }
+
+ private:
+  void* proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallProxyWrapperBase);
+};
+
+template <typename T>
+class CallProxyWrapper : public CallProxyWrapperBase {
+ public:
+  explicit CallProxyWrapper(T* proxy) : CallProxyWrapperBase(proxy) {}
+  CallProxyWrapper(CallProxyWrapper&& other)
+      : CallProxyWrapperBase(std::move(other)) {}
+
+  T* operator->() const { return static_cast<T*>(proxy()); }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CallProxyWrapper);
+};
+
+}  // namespace internal
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_CALL_INTERNAL_H_
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
index 7ba9126..0d24ae2 100644
--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
@@ -14,6 +14,7 @@
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/component_export.h"
+#include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -115,6 +116,12 @@
   void FlushForTesting();
   void FlushAsyncForTesting(base::OnceClosure callback);
 
+#if DCHECK_IS_ON()
+  void SetNextCallLocation(const base::Location& location) {
+    next_call_location_ = location;
+  }
+#endif
+
  private:
   // Maps from the id of a response to the MessageReceiver that handles the
   // response.
@@ -184,6 +191,13 @@
   internal::ControlMessageProxy control_message_proxy_;
   internal::ControlMessageHandler control_message_handler_;
 
+#if DCHECK_IS_ON()
+  // The code location of the the most recent call into a method on this
+  // interface endpoint. This is set *after* the call but *before* any message
+  // is actually transmitted for it.
+  base::Location next_call_location_;
+#endif
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<InterfaceEndpointClient> weak_ptr_factory_;
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 78de3ce4..f9318db 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -239,6 +239,11 @@
 
   InitControllerIfNecessary();
 
+#if DCHECK_IS_ON()
+  // TODO(https://crbug.com/695289): Send |next_call_location_| in a control
+  // message before calling |SendMessage()| below.
+#endif
+
   return controller_->SendMessage(message);
 }
 
@@ -265,6 +270,11 @@
 
   message->set_request_id(request_id);
 
+#if DCHECK_IS_ON()
+  // TODO(https://crbug.com/695289): Send |next_call_location_| in a control
+  // message before calling |SendMessage()| below.
+#endif
+
   bool is_sync = message->has_flag(Message::kFlagIsSync);
   if (!controller_->SendMessage(message))
     return false;
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
index 980b68a..9ed2102 100644
--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
@@ -15,6 +15,7 @@
 #include "base/bind.h"
 #include "base/callback_forward.h"
 #include "base/component_export.h"
+#include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
@@ -59,6 +60,12 @@
     return endpoint_client_ && endpoint_client_->has_pending_responders();
   }
 
+#if DCHECK_IS_ON()
+  void SetNextCallLocation(const base::Location& location) {
+    endpoint_client_->SetNextCallLocation(location);
+  }
+#endif
+
  protected:
   InterfaceEndpointClient* endpoint_client() const {
     return endpoint_client_.get();
@@ -116,6 +123,13 @@
     return proxy_.get();
   }
 
+  void SetNextCallLocation(const base::Location& location) {
+#if DCHECK_IS_ON()
+    ConfigureProxyIfNecessary();
+    InterfacePtrStateBase::SetNextCallLocation(location);
+#endif
+  }
+
   void QueryVersion(const base::Callback<void(uint32_t)>& callback) {
     ConfigureProxyIfNecessary();
     InterfacePtrStateBase::QueryVersion(callback);
diff --git a/mojo/public/cpp/bindings/lib/interface_serialization.h b/mojo/public/cpp/bindings/lib/interface_serialization.h
index 00954de..498308a3 100644
--- a/mojo/public/cpp/bindings/lib/interface_serialization.h
+++ b/mojo/public/cpp/bindings/lib/interface_serialization.h
@@ -16,6 +16,8 @@
 #include "mojo/public/cpp/bindings/lib/bindings_internal.h"
 #include "mojo/public/cpp/bindings/lib/serialization_context.h"
 #include "mojo/public/cpp/bindings/lib/serialization_forward.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/system/handle.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
@@ -115,6 +117,26 @@
 };
 
 template <typename Base, typename T>
+struct Serializer<InterfacePtrDataView<Base>, PendingRemote<T>> {
+  static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+  static void Serialize(PendingRemote<T>& input,
+                        Interface_Data* output,
+                        SerializationContext* context) {
+    context->AddInterfaceInfo(input.TakePipe(), input.version(), output);
+  }
+
+  static bool Deserialize(Interface_Data* input,
+                          PendingRemote<T>* output,
+                          SerializationContext* context) {
+    *output = PendingRemote<T>(
+        context->TakeHandleAs<mojo::MessagePipeHandle>(input->handle),
+        input->version);
+    return true;
+  }
+};
+
+template <typename Base, typename T>
 struct Serializer<InterfaceRequestDataView<Base>, InterfaceRequest<T>> {
   static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
 
@@ -133,6 +155,25 @@
   }
 };
 
+template <typename Base, typename T>
+struct Serializer<InterfaceRequestDataView<Base>, PendingReceiver<T>> {
+  static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+  static void Serialize(PendingReceiver<T>& input,
+                        Handle_Data* output,
+                        SerializationContext* context) {
+    context->AddHandle(ScopedHandle::From(input.TakePipe()), output);
+  }
+
+  static bool Deserialize(Handle_Data* input,
+                          PendingReceiver<T>* output,
+                          SerializationContext* context) {
+    *output =
+        PendingReceiver<T>(context->TakeHandleAs<MessagePipeHandle>(*input));
+    return true;
+  }
+};
+
 }  // namespace internal
 }  // namespace mojo
 
diff --git a/mojo/public/cpp/bindings/pending_receiver.h b/mojo/public/cpp/bindings/pending_receiver.h
new file mode 100644
index 0000000..342d077
--- /dev/null
+++ b/mojo/public/cpp/bindings/pending_receiver.h
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_PENDING_RECEIVER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_PENDING_RECEIVER_H_
+
+#include <utility>
+
+#include "base/macros.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// A PendingReceiver receives and accumulates a queue of incoming Interface
+// method calls made by a single corresponding Remote. PendingReceiver instances
+// may be freely moved to another thread/sequence, or even transferred to
+// another process via a Mojo interface call (see pending_receiver<T> syntax in
+// mojom IDL).
+//
+// This object should eventually be consumed to bind a Receiver, which will then
+// begin dispatching any queued and future incoming method calls to a local
+// implementation of Interface. See Receiver documentation for more details.
+//
+// NOTE: This object is essentially semantic sugar wrapping a message pipe
+// handle that is expected to receive Interface messages from a Remote. As such,
+// consumers who know what they're doing (i.e. who are confident about what lies
+// on the other end of a pipe) may freely convert between a PendingReceiver and
+// a raw message pipe handle.
+template <typename Interface>
+class PendingReceiver {
+ public:
+  // Constructs an invalid PendingReceiver. This object is not entangled with
+  // any Remote and cannot be used to bind a Receiver.
+  //
+  // A valid PendingReceiver is commonly obtained by calling
+  // |Remote::BindNewReceiver()| on an existing unbound Remote instance or less
+  // commonly by calling calling |PendingRemote::MakeReceiver()| on an existing
+  // but invalid PendingRemote instance.
+  PendingReceiver() = default;
+  PendingReceiver(PendingReceiver&&) noexcept = default;
+
+  // Constructs a valid PendingReceiver from a valid raw message pipe handle.
+  explicit PendingReceiver(ScopedMessagePipeHandle pipe)
+      : pipe_(std::move(pipe)) {
+    DCHECK(pipe_.is_valid());
+  }
+
+  ~PendingReceiver() = default;
+
+  PendingReceiver& operator=(PendingReceiver&&) noexcept = default;
+
+  // Indicates whether the PendingReceiver is valid, meaning it can ne used to
+  // bind a Receiver that wants to begin dispatching method calls made by the
+  // entangled Remote.
+  bool is_valid() const { return pipe_.is_valid(); }
+
+  // Resets this PendingReceiver to an invalid state. If it was entangled with a
+  // Remote or PendingRemote, that object remains in a valid state and will
+  // eventually detect that its receiver is gone. Any calls it makes will
+  // effectively be dropped.
+  void reset() { pipe_.reset(); }
+
+  // Takes ownership of this PendingReceiver's message pipe handle. After this
+  // call, the PendingReceiver is no longer in a valid state and can no longer
+  // be used to bind a Receiver.
+  ScopedMessagePipeHandle TakePipe() WARN_UNUSED_RESULT {
+    return std::move(pipe_);
+  }
+
+ private:
+  ScopedMessagePipeHandle pipe_;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingReceiver);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_PENDING_RECEIVER_H_
diff --git a/mojo/public/cpp/bindings/pending_remote.h b/mojo/public/cpp/bindings/pending_remote.h
new file mode 100644
index 0000000..cf37734
--- /dev/null
+++ b/mojo/public/cpp/bindings/pending_remote.h
@@ -0,0 +1,104 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_PENDING_REMOTE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_PENDING_REMOTE_H_
+
+#include <cstdint>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// A valid PendingRemote is entangled with exactly one Receiver or
+// PendingReceiver, and can be consumed to bind a Remote in order to begin
+// issuing method calls to that receiver. See Remote documentation for more
+// details.
+//
+// PendingRemote instances may be freely moved to another thread/sequence, or
+// even transferred to another process via a Mojo interface call (see
+// pending_remote<T> syntax in mojom IDL).
+//
+// NOTE: This object is essentially semantic sugar wrapping a raw message pipe
+// handle that is expected to send Interface messages of a specified version
+// (typically 0) to a Receiver. As such, consumers who know what they're doing
+// (i.e. who are confident about what lies on the other side of a pipe) may
+// freely convert between a PendingRemote and a 2-tuple of
+// [raw message pipe handle, expected interface version number].
+template <typename Interface>
+class PendingRemote {
+ public:
+  // Constructs an invalid PendingRemote. This object is not entangled with any
+  // receiver and cannot be used to bind a Remote.
+  //
+  // A valid PendingRemote is typically obtained by calling
+  // |Receiver::BindNewRemote()| on an existing unbound Receiver instance.
+  //
+  // To simultaneously create a valid PendingRemote and an entangled
+  // PendingReceiver for rarer cases where both objects need to be passed
+  // elsewhere, use the |MakeReceiver()| method defined below.
+  PendingRemote() = default;
+  PendingRemote(PendingRemote&&) noexcept = default;
+
+  // Constructs a valid PendingRemote over a valid raw message pipe handle and
+  // expected interface version number.
+  PendingRemote(ScopedMessagePipeHandle pipe, uint32_t version)
+      : pipe_(std::move(pipe)), version_(version) {
+    DCHECK(pipe_.is_valid());
+  }
+
+  ~PendingRemote() = default;
+
+  PendingRemote& operator=(PendingRemote&&) noexcept = default;
+
+  // Indicates whether the PendingRemote is valid, meaning it can be used to
+  // bind a Remote that wants to begin issuing method calls to be dispatched by
+  // the entangled Receiver.
+  bool is_valid() const { return pipe_.is_valid(); }
+
+  // Resets this PendingRemote to an invalid state. If it was entangled with a
+  // Receiver or PendingReceiver, that object remains in a valid state and will
+  // eventually detect that its remote caller is gone.
+  void reset() {
+    pipe_.reset();
+    version_ = 0;
+  }
+
+  // Takes ownership of this PendingRemote's message pipe handle. After this
+  // call, the PendingRemote is no longer in a valid state and can no longer be
+  // used to bind a Remote.
+  ScopedMessagePipeHandle TakePipe() WARN_UNUSED_RESULT {
+    version_ = 0;
+    return std::move(pipe_);
+  }
+
+  // The version of the interface this Remote is assuming when making method
+  // calls. For the most common case of unversioned mojom interfaces, this is
+  // always zero.
+  uint32_t version() const { return version_; }
+
+  // Creates a new PendingReceiver and entangles this PendingRemote with it.
+  // May only be called on an invalid PendingRemote.
+  PendingReceiver<Interface> MakeReceiver() WARN_UNUSED_RESULT {
+    DCHECK(!is_valid()) << "PendingRemote already has a receiver";
+    MessagePipe pipe;
+    pipe_ = std::move(pipe.handle0);
+    return PendingReceiver<Interface>(std::move(pipe.handle1));
+  }
+
+ private:
+  ScopedMessagePipeHandle pipe_;
+  uint32_t version_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingRemote);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_PENDING_REMOTE_H_
diff --git a/mojo/public/cpp/bindings/receiver.h b/mojo/public/cpp/bindings/receiver.h
new file mode 100644
index 0000000..e97d0aa
--- /dev/null
+++ b/mojo/public/cpp/bindings/receiver.h
@@ -0,0 +1,172 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_H_
+
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/binding_state.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// A Receiver is used to receive and dispatch Interface method calls to a local
+// implementation of Interface. Every Receiver object is permanently linked to
+// an implementation of Interface at construction time. The Receiver begins
+// receiving and scheduling method calls to the implementation once it becomes
+// bound by consuming a PendingReceiver, either at construction time or by
+// calling |Bind()|.
+//
+// Receiver is NOT thread- or sequence- safe and must be used from a single
+// (but otherwise arbitrary) sequence. All bound Receiver objects are associated
+// with a base::SequencedTaskRunner which the Receiver uses exclusively to
+// schedule incoming method calls and disconnection notifications.
+//
+// IMPORTANT: In the name of memory safety, Interface method calls and
+// disconnection notifications scheduled by a Receiver object will NEVER run
+// beyond the lifetime of the Receiver object.
+template <typename Interface,
+          typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
+class Receiver {
+ public:
+  // Typically (and by default) a Receiver uses a raw pointer to reference its
+  // linked Interface implementation object, because typically that
+  // implementation object owns the Receiver. An alternative |ImplRefTraits| may
+  // be provided as a second Receiver template argument in order to use a
+  // different reference type.
+  using ImplPointerType = typename ImplRefTraits::PointerType;
+
+  // Constructs an unbound Receiver linked to |impl| for the duration of the
+  // Receive's lifetime. The Receiver can be bound later by calling |Bind()| or
+  // |BindNewRemote()|. An unbound Receiver does not schedule any asynchronous
+  // tasks.
+  explicit Receiver(ImplPointerType impl) : internal_state_(std::move(impl)) {}
+
+  // Constructs a bound Receiver by consuming |pending_receiver|. The Receiver
+  // is permanently linked to |impl| and will schedule incoming |impl| method
+  // and disconnection notifications on the default SequencedTaskRunner (i.e.
+  // base::SequencedTaskRunnerHandle::Get() at construction time).
+  Receiver(ImplPointerType impl, PendingReceiver<Interface> pending_receiver)
+      : Receiver(std::move(impl), std::move(pending_receiver), nullptr) {}
+
+  // Similar to above but the constructed Receiver schedules all tasks via
+  // |task_runner| instead of the default SequencedTaskRunner. |task_runner|
+  // must run tasks on the same sequence that owns this Receiver.
+  Receiver(ImplPointerType impl,
+           PendingReceiver<Interface> pending_receiver,
+           scoped_refptr<base::SequencedTaskRunner> task_runner)
+      : internal_state_(std::move(impl)) {
+    Bind(std::move(pending_receiver), std::move(task_runner));
+  }
+
+  ~Receiver() = default;
+
+  // Indicates whether this Receiver is bound, meaning it may continue to
+  // receive Interface method calls from a remote caller.
+  //
+  // NOTE: A Receiver is NEVER passively unbound. The only way for it to become
+  // unbound is to explicitly call |reset()| or |Unbind()|.
+  bool is_bound() const { return internal_state_.is_bound(); }
+
+  // Sets a OnceClosure to be invoked if this Receiver is cut off from its
+  // Remote (or PendingRemote). This can happen if the corresponding Remote (or
+  // unconsumed PendingRemote) has been destroyed, or if the Remote sends a
+  // malformed message. Must only be called on a bound Receiver object, and only
+  // remains set as long as the Receiver is both bound and connected.
+  //
+  // If ever invoked, |handler| will be scheduled asynchronously on the
+  // Receiver's bound SequencedTaskRunner.
+  void set_disconnect_handler(base::OnceClosure handler) {
+    internal_state_.set_connection_error_handler(std::move(handler));
+  }
+
+  // Resets this Receiver to an unbound state. An unbound Receiver will NEVER
+  // schedule method calls or disconnection notifications, and any pending tasks
+  // which were scheduled prior to unbinding are effectively cancelled.
+  void reset() { internal_state_.Close(); }
+
+  // Binds this Receiver, connecting it to a new PendingRemote which is
+  // returned for transmission elsewhere (typically to a Remote who will consume
+  // it to start making calls).
+  //
+  // The Receiver will schedule incoming |impl| method calls and disconnection
+  // notifications on the default SequencedTaskRunner (i.e.
+  // base::SequencedTaskRunnerHandle::Get() at the time of this call). Must only
+  // be called on an unbound Receiver.
+  PendingRemote<Interface> BindNewRemote() WARN_UNUSED_RESULT {
+    return BindNewRemote(nullptr);
+  }
+
+  // Like above, but the Receiver will schedule incoming |impl| method calls and
+  // disconnection notifications on |task_runner| rather than on the default
+  // SequencedTaskRunner. Must only be called on an unbound Receiver.
+  // |task_runner| must run tasks on the same sequence that owns this Receiver.
+  PendingRemote<Interface> BindNewRemote(
+      scoped_refptr<base::SequencedTaskRunner> task_runner) WARN_UNUSED_RESULT {
+    DCHECK(!is_bound()) << "Receiver is already bound";
+    PendingRemote<Interface> remote;
+    Bind(remote.MakeReceiver(), std::move(task_runner));
+    return remote;
+  }
+
+  // Binds this Receiver by consuming |pending_receiver|, which must be valid.
+  // Must only be called on an unbound Receiver.
+  //
+  // The newly bound Receiver will schedule incoming |impl| method calls and
+  // disconnection notifications on the default SequencedTaskRunner (i.e.
+  // base::SequencedTaskRunnerHandle::Get() at the time of this call).
+  void Bind(PendingReceiver<Interface> pending_receiver) {
+    DCHECK(pending_receiver.is_valid());
+    Bind(std::move(pending_receiver), nullptr);
+  }
+
+  // Like above, but the newly bound Receiver will schedule incoming |impl|
+  // method calls and disconnection notifications on |task_runner| instead of
+  // the default SequencedTaskRunner. Must only be called on an unbound
+  // Receiver. |task_runner| must run tasks on the same sequence that owns this
+  // Receiver.
+  void Bind(PendingReceiver<Interface> pending_receiver,
+            scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    internal_state_.Bind(pending_receiver.TakePipe(), std::move(task_runner));
+  }
+
+  // Unbinds this Receiver, preventing any further |impl| method calls or
+  // disconnection notifications from being scheduled by it. Any such tasks that
+  // were scheduled prior to unbinding are effectively cancelled.
+  //
+  // Returns a PendingReceiver which remains connected to this receiver's
+  // Remote and which may be transferred elsewhere and consumed by another
+  // Receiver. Any messages received but not actually dispatched by this
+  // Receiver remain intact within the returned PendingReceiver and can be
+  // dispatched by whomever binds with it later.
+  //
+  // Note that a Receiver should not be unbound while there are still living
+  // response callbacks that haven't been invoked, as once the Receiver is
+  // unbound those response callbacks are no longer valid and the Remote will
+  // never be able to receive its expected responses.
+  PendingReceiver<Interface> Unbind() {
+    CHECK(!internal_state_.HasAssociatedInterfaces());
+    return PendingReceiver<Interface>(
+        internal_state_.Unbind().PassMessagePipe());
+  }
+
+ private:
+  internal::BindingState<Interface, ImplRefTraits> internal_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(Receiver);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_RECEIVER_H_
diff --git a/mojo/public/cpp/bindings/remote.h b/mojo/public/cpp/bindings/remote.h
new file mode 100644
index 0000000..eeb7d93
--- /dev/null
+++ b/mojo/public/cpp/bindings/remote.h
@@ -0,0 +1,247 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_REMOTE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_REMOTE_H_
+
+#include <cstdint>
+#include <utility>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "mojo/public/cpp/bindings/call_internal.h"
+#include "mojo/public/cpp/bindings/interface_ptr_info.h"
+#include "mojo/public/cpp/bindings/lib/interface_ptr_state.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+
+// A Remote is used to issue Interface method calls to a single connected
+// Receiver or PendingReceiver. The Remote must be bound in order to issue those
+// method calls, and it becomes bound by consuming a PendingRemote either at
+// construction time or by calling |Bind()|.
+//
+// Remote is NOT thread- or sequence-safe and must be used on a single
+// (but otherwise arbitrary) sequence. All bound Remote objects are associated
+// with a base::SequenceTaskRunner which the Remote uses exclusively to schedule
+// response callbacks and disconnection notifications.
+//
+// The most common ways to bind a Remote are to consume to a PendingRemote
+// received via some IPC, or to call |BindNewReceiver()| and send the returned
+// PendingReceiver somewhere useful (i.e., to a remote Receiver who will consume
+// it). For example:
+//
+//     mojo::Remote<mojom::Widget> widget;
+//     widget_factory->CreateWidget(widget.BindNewReceiver());
+//     widget.rpc(FROM_HERE)->Click();
+//
+// IMPORTANT: There are some things to be aware of regarding Interface method
+// calls as they relate to Remote object lifetime:
+//
+//   - Interface method calls issued immediately before the destruction of a
+//     Remote ARE guaranteed to be transmitted to the remote's receiver as long
+//     as the receiver itself remains alive, either as a Receiver or a
+//     PendingReceiver.
+//
+//   - In the name of memory safety, Interface method response callbacks (and in
+//     general ANY tasks which can be scheduled by a Remote) will NEVER
+//     be dispatched beyond the lifetime of the Remote object. As such, if
+//     you make a call and you need its reply, you must keep the Remote alive
+//     until the reply is received.
+template <typename Interface>
+class Remote {
+ public:
+  // Constructs an unbound Remote. This object cannot issue Interface method
+  // calls and does not schedule any tasks.
+  Remote() = default;
+  Remote(Remote&& other) noexcept {
+    internal_state_.Swap(&other.internal_state_);
+  }
+
+  // Constructs a new Remote which is bound from |pending_remote| and which
+  // schedules response callbacks and disconnection notifications on the default
+  // SequencedTaskRunner (i.e., base::SequencedTaskRunnerHandle::Get() at
+  // construction time).
+  explicit Remote(PendingRemote<Interface> pending_remote)
+      : Remote(std::move(pending_remote), nullptr) {}
+
+  // Constructs a new Remote which is bound from |pending_remote| and which
+  // schedules response callbacks and disconnection notifications on
+  // |task_runner|. |task_runner| must run tasks on the same sequence that owns
+  // this Remote.
+  Remote(PendingRemote<Interface> pending_remote,
+         scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(pending_remote.is_valid());
+    Bind(std::move(pending_remote), std::move(task_runner));
+  }
+
+  ~Remote() = default;
+
+  // Issue a method call to the remote implementation of Interface. If the
+  // remote end is still a PendingReceiver, the call will be queued within that
+  // object. If the remote end is bound to a live Receiver, the call will
+  // eventually be dispatched in the order it was received.
+  //
+  // If the Remote is no longer connected because the receiver has been
+  // destroyed, the call will be dropped.
+  internal::CallProxyWrapper<Interface> rpc(const base::Location& from_here) {
+    internal_state_.SetNextCallLocation(from_here);
+    return internal::CallProxyWrapper<Interface>(internal_state_.instance());
+  }
+
+  // Exposes access to callable Interface methods directed at this Remote's
+  // receiver. Must only be called on a bound Remote.
+  //
+  // Direct use of this accessor is discouraged and callers should prefer to use
+  // |rpc()| instead when making remote calls. Using |rpc()| can provide
+  // useful call-site attribution in DCHECK-enabled builds and makes IPC call
+  // sites easier to grok.
+  typename Interface::Proxy_* get() const {
+    DCHECK(is_bound())
+        << "Cannot issue Interface method calls on an unbound Remote";
+    return internal_state_.instance();
+  }
+
+  // Shorthand form of |get()|. See above.
+  //
+  // TODO(https://crbug.com/934883): Figure out whether to disallow use of this
+  // operator in certain environments.
+  typename Interface::Proxy_* operator->() const { return get(); }
+
+  // Indicates whether this Remote is bound and thus can issue Interface method
+  // calls via e.g. |rpc()|.
+  //
+  // NOTE: The state of being "bound" should not be confused with the state of
+  // being "connected" (see |is_connected()| below). A Remote is NEVER passively
+  // unbound and the only way for it to become unbound is to explicitly call
+  // |reset()| or |Unbind()|. As such, unless you make explicit calls to those
+  // methods, it is always safe to assume that a Remote you've bound will remain
+  // bound and callable.
+  bool is_bound() const { return internal_state_.is_bound(); }
+
+  // Indicates whether this Remote is connected to a receiver. Must only be
+  // called on a bound Remote. If this returns |true|, method calls made by this
+  // Remote may eventually end up at the connected receiver (though it's of
+  // course possible for this call to race with disconnection). If this returns
+  // |false| however, all future Interface method calls on this Remote will be
+  // silently dropped.
+  //
+  // A bound Remote becomes disconnected automatically either when its receiver
+  // is destroyed, or when it receives a malformed or otherwise unexpected
+  // response message from the receiver.
+  //
+  // NOTE: The state of being "bound" should not be confused with the state of
+  // being "connected". See |is_bound()| above.
+  bool is_connected() const {
+    DCHECK(is_bound());
+    return !internal_state_.encountered_error();
+  }
+
+  // Sets a Closure to be invoked if this Remote is cut off from its receiver.
+  // This can happen if the corresponding Receiver (or unconsumed
+  // PendingReceiver) is destroyed, or if the Receiver sends a malformed or
+  // otherwise unexpected response message to this Remote. Must only be called
+  // on a bound Remote object, and only remains set as long as the Remote is
+  // both bound and connected.
+  //
+  // If invoked at all, |handler| will be scheduled asynchronously using the
+  // Remote's bound SequencedTaskRunner.
+  void set_disconnect_handler(base::OnceClosure handler) {
+    if (is_connected())
+      internal_state_.set_connection_error_handler(std::move(handler));
+  }
+
+  // Resets this Remote to an unbound state. To reset the Remote and recover an
+  // PendingRemote that can be bound again later, use |Unbind()| instead.
+  void reset() {
+    State doomed_state;
+    internal_state_.Swap(&doomed_state);
+  }
+
+  // Binds this Remote, connecting it to a new PendingReceiver which is
+  // returned for transmission to some Receiver which can bind it. The Remote
+  // will schedule any response callbacks or disconnection notifications on the
+  // default SequencedTaskRunner (i.e. base::SequencedTaskRunnerHandle::Get() at
+  // the time of this call). Must only be called on an unbound Remote.
+  PendingReceiver<Interface> BindNewReceiver() WARN_UNUSED_RESULT {
+    return BindNewReceiver(nullptr);
+  }
+
+  // Like above, but the Remote will schedule response callbacks and
+  // disconnection notifications on |task_runner| instead of the default
+  // SequencedTaskRunner. |task_runner| must run tasks on the same sequence that
+  // owns this Remote.
+  PendingReceiver<Interface> BindNewReceiver(
+      scoped_refptr<base::SequencedTaskRunner> task_runner) WARN_UNUSED_RESULT {
+    MessagePipe pipe;
+    Bind(PendingRemote<Interface>(std::move(pipe.handle0), 0),
+         std::move(task_runner));
+    return PendingReceiver<Interface>(std::move(pipe.handle1));
+  }
+
+  // Binds this Remote by consuming |pending_remote|, which must be valid. The
+  // Remote will schedule any response callbacks or disconnection notifications
+  // on the default SequencedTaskRunner (i.e.
+  // base::SequencedTaskRunnerHandle::Get() at the time of this call). Must only
+  // be called on an unbound Remote.
+  void Bind(PendingRemote<Interface> pending_remote) {
+    DCHECK(pending_remote.is_valid());
+    Bind(std::move(pending_remote), nullptr);
+  }
+
+  // Like above, but the Remote will schedule response callbacks and
+  // disconnection notifications on |task_runner| instead of the default
+  // SequencedTaskRunner. Must only be called on an unbound Remote.
+  // |task_runner| must run tasks on the same sequence that owns this Remote.
+  void Bind(PendingRemote<Interface> pending_remote,
+            scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    DCHECK(!is_bound()) << "Remote is already bound";
+    internal_state_.Bind(InterfacePtrInfo<Interface>(pending_remote.TakePipe(),
+                                                     pending_remote.version()),
+                         std::move(task_runner));
+
+    // Force the internal state to configure its proxy. Unlike InterfacePtr we
+    // do not use Remote in transit, so binding to a pipe handle can also imply
+    // binding to a SequencedTaskRunner and observing pipe handle state. This
+    // allows for e.g. |is_connected()| to be a more reliable API than
+    // |InterfacePtr::encountered_error()|.
+    ignore_result(internal_state_.instance());
+  }
+
+  // Unbinds this Remote, rendering it unable to issue further Interface method
+  // calls. Returns a PendingRemote which may be passed across threads or
+  // processes and consumed by another Remote elsewhere.
+  //
+  // Note that it is an error (the bad, crashy kind of error) to attempt to
+  // |Unbind()| a Remote which is awaiting one or more responses to previously
+  // issued Interface method calls. Calling this method should only be
+  // considered in cases where satisfaction of that constraint can be proven.
+  //
+  // Must only be called on a bound Remote.
+  PendingRemote<Interface> Unbind() WARN_UNUSED_RESULT {
+    DCHECK(is_bound());
+    CHECK(!internal_state_.has_unbound_callbacks());
+    State state;
+    internal_state_.Swap(&state);
+    InterfacePtrInfo<Interface> info = state.PassInterface();
+    return PendingRemote<Interface>(info.PassHandle(), info.version());
+  }
+
+ private:
+  using State = internal::InterfacePtrState<Interface>;
+  mutable State internal_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(Remote);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_PUBLIC_CPP_BINDINGS_REMOTE_H_
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
index dd9db3e..a73a8736 100644
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -30,6 +30,7 @@
     "message_queue.h",
     "multiplex_router_unittest.cc",
     "native_struct_unittest.cc",
+    "new_endpoint_types_unittest.cc",
     "report_bad_message_unittest.cc",
     "request_response_unittest.cc",
     "router_test_util.cc",
@@ -54,6 +55,7 @@
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//mojo/public/cpp/test_support:test_utils",
+    "//mojo/public/interfaces/bindings/tests:other_test_interfaces",
     "//mojo/public/interfaces/bindings/tests:test_associated_interfaces",
     "//mojo/public/interfaces/bindings/tests:test_export_component",
     "//mojo/public/interfaces/bindings/tests:test_export_component2",
diff --git a/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc b/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc
new file mode 100644
index 0000000..59babcc
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/new_endpoint_types_unittest.cc
@@ -0,0 +1,237 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/interfaces/bindings/tests/new_endpoint_types.test-mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace new_endpoint_types {
+
+class FactoryImpl;
+
+class WidgetImpl : public mojom::Widget {
+ public:
+  WidgetImpl(FactoryImpl* factory,
+             mojo::PendingReceiver<mojom::Widget> receiver,
+             mojo::PendingRemote<mojom::WidgetClient> client)
+      : factory_(factory),
+        receiver_(this, std::move(receiver)),
+        client_(std::move(client)) {
+    client_.rpc(FROM_HERE)->OnInitialized();
+    receiver_.set_disconnect_handler(
+        base::BindOnce(&WidgetImpl::OnDisconnect, base::Unretained(this)));
+  }
+
+  ~WidgetImpl() override = default;
+
+  // mojom::Widget:
+  void Click() override {
+    for (auto& observer : observers_)
+      observer.rpc(FROM_HERE)->OnClick();
+  }
+
+  void AddObserver(
+      mojo::PendingRemote<mojom::WidgetObserver> observer) override {
+    observers_.emplace_back(std::move(observer));
+  }
+
+ private:
+  void OnDisconnect();
+
+  FactoryImpl* const factory_;
+  mojo::Receiver<mojom::Widget> receiver_;
+  mojo::Remote<mojom::WidgetClient> client_;
+  std::vector<mojo::Remote<mojom::WidgetObserver>> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(WidgetImpl);
+};
+
+class FactoryImpl : public mojom::WidgetFactory {
+ public:
+  explicit FactoryImpl(mojo::PendingReceiver<mojom::WidgetFactory> receiver)
+      : receiver_(this, std::move(receiver)) {}
+  ~FactoryImpl() override = default;
+
+  // mojom::WidgetFactory:
+  void CreateWidget(mojo::PendingReceiver<mojom::Widget> receiver,
+                    mojo::PendingRemote<mojom::WidgetClient> client) override {
+    widgets_.push_back(std::make_unique<WidgetImpl>(this, std::move(receiver),
+                                                    std::move(client)));
+  }
+
+  void DestroyWidget(WidgetImpl* widget) {
+    for (auto it = widgets_.begin(); it != widgets_.end(); ++it) {
+      if (it->get() == widget) {
+        widgets_.erase(it);
+        return;
+      }
+    }
+  }
+
+ private:
+  mojo::Receiver<mojom::WidgetFactory> receiver_;
+  std::vector<std::unique_ptr<WidgetImpl>> widgets_;
+
+  DISALLOW_COPY_AND_ASSIGN(FactoryImpl);
+};
+
+void WidgetImpl::OnDisconnect() {
+  // Deletes |this|.
+  factory_->DestroyWidget(this);
+}
+
+class ClientImpl : public mojom::WidgetClient {
+ public:
+  ClientImpl() = default;
+  ~ClientImpl() override = default;
+
+  mojo::PendingRemote<mojom::WidgetClient> BindNewRemote() {
+    return receiver_.BindNewRemote();
+  }
+
+  void WaitForInitialize() { wait_loop_.Run(); }
+
+  // mojom::WidgetClient:
+  void OnInitialized() override { wait_loop_.Quit(); }
+
+ private:
+  mojo::Receiver<mojom::WidgetClient> receiver_{this};
+  base::RunLoop wait_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClientImpl);
+};
+
+class ObserverImpl : public mojom::WidgetObserver {
+ public:
+  ObserverImpl() = default;
+  ~ObserverImpl() override = default;
+
+  mojo::PendingRemote<mojom::WidgetObserver> BindNewRemote() {
+    auto remote = receiver_.BindNewRemote();
+    receiver_.set_disconnect_handler(
+        base::BindOnce(&ObserverImpl::OnDisconnect, base::Unretained(this)));
+    return remote;
+  }
+
+  void WaitForClick() { click_loop_.Run(); }
+  void WaitForDisconnect() { disconnect_loop_.Run(); }
+
+  // mojom::WidgetObserver:
+  void OnClick() override { click_loop_.Quit(); }
+
+ private:
+  void OnDisconnect() { disconnect_loop_.Quit(); }
+
+  mojo::Receiver<mojom::WidgetObserver> receiver_{this};
+  base::RunLoop click_loop_;
+  base::RunLoop disconnect_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(ObserverImpl);
+};
+
+TEST(NewEndpointTypesTest, BasicUsage) {
+  // A simple smoke/compile test for new bindings endpoint types. Used to
+  // demonstrate look & feel as well as to ensure basic completeness and
+  // correctness.
+
+  base::test::ScopedTaskEnvironment task_environment;
+
+  // A Remote<T> exposes a callable T interface which sends messages to a remote
+  // implementation of T. Here we create a new unbound Remote which will control
+  // a remote implementation of |mojom::WidgetFactory|.
+  mojo::Remote<mojom::WidgetFactory> factory;
+  EXPECT_FALSE(factory.is_bound());
+
+  // |factory_impl| is a concrete implementation of |mojom::WidgetFactory|. With
+  // Mojo interfaces, the implementation can live in the same process as the
+  // Remote<T> calling it, or it can live in another process. For simplicity in
+  // this test we have the implementation living in the test process.
+  //
+  // |BindNewReceiver()| creates a new message pipe to carry
+  // |mojom:WidgetFactory| interface messages. It binds one end to the
+  // |factory| Remote above, and the other end is passed to |factory_impl| so
+  // it can receive messages.
+  FactoryImpl factory_impl(factory.BindNewReceiver());
+  EXPECT_TRUE(factory.is_bound());
+
+  // Similar to above, we create another Remote. this time to control a
+  // |mojom::Widget| implementation somewhere.
+  mojo::Remote<mojom::Widget> widget;
+
+  // |client| is an implementation of |mojom::WidgetClient|. This is a common
+  // pattern for Mojo interfaces -- to have a Remote for some Foo interface
+  // living alongside a corresponding implementation of a FooClient interface.
+  // The pattern allows for two-way communication using separate but
+  // closely-related types of endpoints.
+  ClientImpl client;
+
+  // Here we send two message pipes to the remote factory. This |CreateWidget|
+  // call will be dispatched asynchronously to |factory_impl| via Mojo. Notice
+  // that, inline, we create a new |mojom::Widget| pipe as well as a new
+  // |mojom::WidgetClient| pipe. The Widget's Receiver endpoint is passed to
+  // the factory implementation, as is the WidgetClient's Remote endpoint.
+  // This allows the factory to bind and begin receiving Widget messages on
+  // one pipe, and to bind and begin sending WidgetClient messages on the other.
+  factory.rpc(FROM_HERE)->CreateWidget(widget.BindNewReceiver(),
+                                       client.BindNewRemote());
+
+  // Similar to |client| above, we create some implementations of
+  // |mojom::WidgetObserver| here to receive messages from Remote
+  // WidgetObserver caller on the factory implementation's side of the world.
+  ObserverImpl observer1, observer2;
+
+  // Similar to the |CreateWidget| call above, here we create new WidgetObserver
+  // pipes (one for each impl object) and pass their Remote ends to the remote
+  // Widget implementation to bind and use. This allows the remote Widget
+  // implementation to send messages to both |observer1| and |observer2|.
+  widget.rpc(FROM_HERE)->AddObserver(observer1.BindNewRemote());
+  widget.rpc(FROM_HERE)->AddObserver(observer2.BindNewRemote());
+
+  // When the FactoryImpl asynchronously receives our |CreateWidget| call, it
+  // will send back a |mojom::WidgetClient::Initialize()| message to our
+  // |client| object using the Remote passed to |CreateWidget|. This waits for
+  // that message.
+  client.WaitForInitialize();
+
+  // Send another message, this time to the remote Widget implementation.
+  widget.rpc(FROM_HERE)->Click();
+
+  // When the remote Widget implementation receives a |Click()| message, it
+  // broadcasts a |mojom::WidgetObserver::OnClick()| event to all registered
+  // WidgetObservers on the Widget. We wait for each of our observers to
+  // receive that message here.
+  observer1.WaitForClick();
+  observer2.WaitForClick();
+
+  // Remotes (and Receivers, for that matter) remain bound until explicitly
+  // unbound by their owner.
+  widget.reset();
+  EXPECT_FALSE(widget.is_bound());
+
+  // Resetting the Remote<Widget> above eventually triggers the remote Widget
+  // implementation's disconnection handler. That handler in turn tears down
+  // the Widget implementation, including the Remote<WidgetObserver> endpoints
+  // it owns. This in turn will eventually trigger our local WidgetObserver
+  // instances' disconnection handlers. We wait for that to happen here.
+  observer1.WaitForDisconnect();
+  observer2.WaitForDisconnect();
+}
+
+}  // namespace new_endpoint_types
+}  // namespace test
+}  // namespace mojo
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
index 0cfecb8..4f58b83 100644
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -484,3 +484,12 @@
     "echo_import/echo_import.mojom",
   ]
 }
+
+# These could probably be merged with "test_interfaces" at some point.
+mojom("other_test_interfaces") {
+  testonly = true
+  cpp_only = true
+  sources = [
+    "new_endpoint_types.test-mojom",
+  ]
+}
diff --git a/mojo/public/interfaces/bindings/tests/new_endpoint_types.test-mojom b/mojo/public/interfaces/bindings/tests/new_endpoint_types.test-mojom
new file mode 100644
index 0000000..91309dc
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/new_endpoint_types.test-mojom
@@ -0,0 +1,24 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module mojo.test.new_endpoint_types.mojom;
+
+interface WidgetObserver {
+  OnClick();
+};
+
+interface Widget {
+  Click();
+
+  AddObserver(pending_remote<WidgetObserver> observer);
+};
+
+interface WidgetClient {
+  OnInitialized();
+};
+
+interface WidgetFactory {
+  CreateWidget(pending_receiver<Widget> receiver,
+               pending_remote<WidgetClient> client);
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
index 687eea84..42d4ad12 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
@@ -2,6 +2,8 @@
 class {{export_attribute}} {{interface.name}}Proxy
     : public {{interface.name}} {
  public:
+  using InterfaceType = {{interface.name}};
+
   explicit {{interface.name}}Proxy(mojo::MessageReceiverWithResponder* receiver);
 
 {%- for method in interface.methods %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-forward.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-forward.h.tmpl
index 1cba203..a14d511 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-forward.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-forward.h.tmpl
@@ -42,6 +42,8 @@
 #include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "mojo/public/cpp/bindings/lib/control_message_handler.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
 #include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
 {%-   if for_blink %}
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index 0d6dc76..2e6e20a 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -224,7 +224,9 @@
           mojom.IsAnyHandleKind(kind) or
           mojom.IsInterfaceKind(kind) or
           mojom.IsInterfaceRequestKind(kind) or
-          mojom.IsAssociatedKind(kind)):
+          mojom.IsAssociatedKind(kind) or
+          mojom.IsPendingRemoteKind(kind) or
+          mojom.IsPendingReceiverKind(kind)):
         pass
       elif mojom.IsArrayKind(kind):
         AddKind(kind.kind)
@@ -585,6 +587,12 @@
     if mojom.IsInterfaceRequestKind(kind):
       return "%sRequest" % self._GetNameForKind(
           kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+    if mojom.IsPendingRemoteKind(kind):
+      return "mojo::PendingRemote<%s>" % self._GetNameForKind(
+          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+    if mojom.IsPendingReceiverKind(kind):
+      return "mojo::PendingReceiver<%s>" % self._GetNameForKind(
+          kind.kind, add_same_module_namespaces=add_same_module_namespaces)
     if mojom.IsAssociatedInterfaceKind(kind):
       return "%sAssociatedPtrInfo" % self._GetNameForKind(
           kind.kind, add_same_module_namespaces=add_same_module_namespaces)
@@ -671,9 +679,9 @@
       return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" %
               (self._GetCppFieldType(kind.key_kind),
                self._GetCppFieldType(kind.value_kind)))
-    if mojom.IsInterfaceKind(kind):
+    if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
       return "mojo::internal::Interface_Data"
-    if mojom.IsInterfaceRequestKind(kind):
+    if mojom.IsInterfaceRequestKind(kind) or mojom.IsPendingReceiverKind(kind):
       return "mojo::internal::Handle_Data"
     if mojom.IsAssociatedInterfaceKind(kind):
       return "mojo::internal::AssociatedInterface_Data"
@@ -889,6 +897,12 @@
       return "%sPtrDataView" % _GetName(kind)
     if mojom.IsInterfaceRequestKind(kind):
       return "%sRequestDataView" % _GetName(kind.kind)
+    if mojom.IsPendingRemoteKind(kind):
+      return ("mojo::InterfacePtrDataView<%sInterfaceBase>" %
+              _GetName(kind.kind))
+    if mojom.IsPendingReceiverKind(kind):
+      return ("mojo::InterfaceRequestDataView<%sInterfaceBase>" %
+              _GetName(kind.kind))
     if mojom.IsAssociatedInterfaceKind(kind):
       return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind)
     if mojom.IsAssociatedInterfaceRequestKind(kind):
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 7edfa3fd..ac896c8 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -208,6 +208,17 @@
 #   cpp_only (optional)
 #       If set to true, only the C++ bindings targets will be generated.
 #
+#       NOTE: If the global |enable_ipc_fuzzer| build arg is true, JS bindings
+#       will still be generated even when |cpp_only| is set to |true|, unless
+#       you also set |enable_fuzzing| to |false| in your mojom target.
+#
+#   enable_fuzzing (optional)
+#       Enables generation of fuzzing sources for the target if the global build
+#       arg |enable_ipc_fuzzer| is also set to |true|. Defaults to |true|. If
+#       fuzzing generation is enabled for a target, the target will always
+#       generate JS bindings even if |cpp_only| is set to |true|. See note
+#       above.
+#
 #   support_lazy_serialization (optional)
 #       If set to |true|, generated C++ bindings will effectively prefer to
 #       transmit messages in an unserialized form when going between endpoints
@@ -408,6 +419,10 @@
 
   write_file("$target_gen_dir/$target_name.deps_sources_list", deps_sources)
 
+  generate_fuzzing = enable_ipc_fuzzer && (!defined(invoker.enable_fuzzing) ||
+                                           invoker.enable_fuzzing) &&
+                     (!defined(invoker.testonly) || !invoker.testonly)
+
   if (defined(invoker.sources)) {
     parser_target_name = "${target_name}__parser"
     enabled_features = []
@@ -1092,7 +1107,7 @@
         public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
       }
 
-      if (enable_ipc_fuzzer) {
+      if (generate_fuzzing) {
         # Generate JS bindings by default if IPC fuzzer is enabled.
         public_deps += [ ":$js_data_deps_target_name" ]
       }
@@ -1191,7 +1206,7 @@
     }
   }
 
-  if (enable_ipc_fuzzer || !defined(invoker.cpp_only) || !invoker.cpp_only) {
+  if (generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) {
     if (defined(invoker.sources)) {
       generator_js_target_name = "${target_name}_js__generator"
       generator_js_lite_outputs =
@@ -1237,7 +1252,7 @@
           args += message_scrambling_args
         }
 
-        if (enable_ipc_fuzzer) {
+        if (generate_fuzzing) {
           args += [ "--generate_fuzzing" ]
         }
       }
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
index db45e33..79264a0 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/module.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
@@ -490,6 +490,30 @@
       return GenericRepr(self, {'key_kind': True, 'value_kind': True})
 
 
+class PendingRemote(ReferenceKind):
+  ReferenceKind.AddSharedProperty('kind')
+
+  def __init__(self, kind=None):
+    if not isinstance(kind, Interface):
+      raise Exception(
+          'pending_remote<T> requires T to be an interface type. Got %r' %
+          kind.spec)
+    ReferenceKind.__init__(self, 'rmt:' + kind.spec)
+    self.kind = kind
+
+
+class PendingReceiver(ReferenceKind):
+  ReferenceKind.AddSharedProperty('kind')
+
+  def __init__(self, kind=None):
+    if not isinstance(kind, Interface):
+      raise Exception(
+          'pending_receiver<T> requires T to be an interface type. Got %r' %
+          kind.spec)
+    ReferenceKind.__init__(self, 'rcv:' + kind.spec)
+    self.kind = kind
+
+
 class InterfaceRequest(ReferenceKind):
   ReferenceKind.AddSharedProperty('kind')
 
@@ -838,6 +862,13 @@
 def IsAssociatedInterfaceRequestKind(kind):
   return isinstance(kind, AssociatedInterfaceRequest)
 
+def IsPendingRemoteKind(kind):
+  return isinstance(kind, PendingRemote)
+
+
+def IsPendingReceiverKind(kind):
+  return isinstance(kind, PendingReceiver)
+
 
 def IsEnumKind(kind):
   return isinstance(kind, Enum)
@@ -875,7 +906,8 @@
 
 def IsAnyInterfaceKind(kind):
   return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind) or
-          IsAssociatedKind(kind))
+          IsAssociatedKind(kind) or IsPendingRemoteKind(kind) or
+          IsPendingReceiverKind(kind))
 
 
 def IsAnyHandleOrInterfaceKind(kind):
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
index e4204a0..9187364 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
@@ -44,11 +44,12 @@
   @classmethod
   def GetSizeForKind(cls, kind):
     if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct,
-                         mojom.Interface, mojom.AssociatedInterface)):
+                         mojom.Interface, mojom.AssociatedInterface,
+                         mojom.PendingRemote)):
       return 8
     if isinstance(kind, mojom.Union):
       return 16
-    if isinstance(kind, mojom.InterfaceRequest):
+    if isinstance(kind, (mojom.InterfaceRequest, mojom.PendingReceiver)):
       kind = mojom.MSGPIPE
     if isinstance(kind, mojom.AssociatedInterfaceRequest):
       return 4
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
index 94fe2a6..cdc9d0e7 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
@@ -69,7 +69,7 @@
     base_kind = _MapKind(kind[0:-1])
     # NOTE: This doesn't rule out enum types. Those will be detected later, when
     # cross-reference is established.
-    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso')
+    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv')
     if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
       raise Exception(
           'A type (spec "%s") cannot be made nullable' % base_kind)
@@ -87,6 +87,12 @@
   if kind.startswith('asso<'):
     assert kind.endswith('>')
     return 'asso:' + _MapKind(kind[5:-1])
+  if kind.startswith('rmt<'):
+    assert kind.endswith('>')
+    return 'rmt:' + _MapKind(kind[4:-1])
+  if kind.startswith('rcv<'):
+    assert kind.endswith('>')
+    return 'rcv:' + _MapKind(kind[4:-1])
   if kind in map_to_kind:
     return map_to_kind[kind]
   return 'x:' + kind
@@ -202,6 +208,10 @@
     kind = mojom.Array(_Kind(kinds, spec[colon+1:], scope), length)
   elif spec.startswith('r:'):
     kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope))
+  elif spec.startswith('rmt:'):
+    kind = mojom.PendingRemote(_Kind(kinds, spec[4:], scope))
+  elif spec.startswith('rcv:'):
+    kind = mojom.PendingReceiver(_Kind(kinds, spec[4:], scope))
   elif spec.startswith('m['):
     # Isolate the two types from their brackets.
 
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
index 06354b1..9a30d33 100644
--- a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py
@@ -65,7 +65,9 @@
     'DEFAULT',
     'ARRAY',
     'MAP',
-    'ASSOCIATED'
+    'ASSOCIATED',
+    'PENDING_REMOTE',
+    'PENDING_RECEIVER'
   )
 
   keyword_map = {}
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
index b9f10dc..d4961274 100644
--- a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
@@ -271,7 +271,9 @@
     p[0] = p[1]
 
   def p_basictypename(self, p):
-    """basictypename : identifier
+    """basictypename : remotetype
+                     | receivertype
+                     | identifier
                      | ASSOCIATED identifier
                      | handletype"""
     if len(p) == 2:
@@ -279,6 +281,14 @@
     else:
       p[0] = "asso<" + p[2] + ">"
 
+  def p_remotetype(self, p):
+    """remotetype : PENDING_REMOTE LANGLE identifier RANGLE"""
+    p[0] = "rmt<%s>" % p[3]
+
+  def p_receivertype(self, p):
+    """receivertype : PENDING_RECEIVER LANGLE identifier RANGLE"""
+    p[0] = "rcv<%s>" % p[3]
+
   def p_handletype(self, p):
     """handletype : HANDLE
                   | HANDLE LANGLE NAME RANGLE"""
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn
index 4c94fd5..989877f 100644
--- a/net/dns/BUILD.gn
+++ b/net/dns/BUILD.gn
@@ -139,9 +139,6 @@
   friend = [
     # chrome/browser/io_thread.cc
     # Used to build in-process HostResolver when network service disabled.
-    #
-    # chrome/browser/net/dns_probe_service.cc
-    # TODO(crbug.com/874660): Remove once migrated to network service IPC.
     "//chrome/browser",
 
     # chromecast/browser/url_request_context_factory.cc
@@ -261,11 +258,6 @@
     # chrome/browser/local_discovery/service_discovery_client_impl.cc
     # Result parsing utilities for parsing results read through MdnsClient.
     # TODO(crbug.com/874662): Remove once migrated to network service.
-    #
-    # chrome/browser/net/dns_probe_runner.cc
-    # chrome/browser/net/dns_probe_service.cc
-    # DNS lookups using DnsClient.
-    # TODO(crbug.com/874660): Remove once migrated to network service.
     "//chrome/browser",
 
     # chrome/browser/chromeos/smb_client/discovery/mdns_host_locator.cc
diff --git a/printing/buildflags/buildflags.gni b/printing/buildflags/buildflags.gni
index d5c572ee..2c449696 100644
--- a/printing/buildflags/buildflags.gni
+++ b/printing/buildflags/buildflags.gni
@@ -16,9 +16,9 @@
   # enable_basic_printing within the same declare_args() block.
   enable_print_preview = !is_android && !is_chromecast && !is_ios && !is_fuchsia
 
-  if (use_fuzzing_engine) {
+  if (use_fuzzing_engine && is_linux) {
     # For fuzzing, just restrict to chromeos and linux.
-    use_cups = is_linux
+    use_cups = true
   } else {
     use_cups = (is_desktop_linux || is_mac) && !is_chromecast && !is_fuchsia
   }
diff --git a/services/device/public/cpp/hid/hid_collection.cc b/services/device/public/cpp/hid/hid_collection.cc
index c77ffc8f..71d509e 100644
--- a/services/device/public/cpp/hid/hid_collection.cc
+++ b/services/device/public/cpp/hid/hid_collection.cc
@@ -204,10 +204,9 @@
   report->push_back(HidReportItem::Create(tag, report_info, state));
 }
 
-mojom::HidCollectionInfoPtr HidCollection::GetDetails(
-    size_t* max_input_report_bits,
-    size_t* max_output_report_bits,
-    size_t* max_feature_report_bits) {
+void HidCollection::GetMaxReportSizes(size_t* max_input_report_bits,
+                                      size_t* max_output_report_bits,
+                                      size_t* max_feature_report_bits) const {
   DCHECK(max_input_report_bits);
   DCHECK(max_output_report_bits);
   DCHECK(max_feature_report_bits);
@@ -253,7 +252,39 @@
           std::max(entry.max_report_bits, size_t{report_bits});
     }
   }
-  return collection_info;
+}
+
+mojom::HidCollectionInfoPtr HidCollection::ToMojo() const {
+  auto collection = mojom::HidCollectionInfo::New();
+  struct {
+    const std::unordered_map<uint8_t, HidReport>& in;
+    std::vector<mojom::HidReportDescriptionPtr>& out;
+  } report_lists[]{
+      {input_reports_, collection->input_reports},
+      {output_reports_, collection->output_reports},
+      {feature_reports_, collection->feature_reports},
+  };
+
+  collection->usage =
+      mojom::HidUsageAndPage::New(usage_.usage, usage_.usage_page);
+  collection->report_ids.insert(collection->report_ids.end(),
+                                report_ids_.begin(), report_ids_.end());
+  collection->collection_type = collection_type_;
+
+  for (const auto& report_list : report_lists) {
+    for (const auto& report : report_list.in) {
+      auto report_description = mojom::HidReportDescription::New();
+      report_description->report_id = report.first;
+      for (const auto& item : report.second)
+        report_description->items.push_back(item->ToMojo());
+      report_list.out.push_back(std::move(report_description));
+    }
+  }
+
+  for (const auto& child : children_)
+    collection->children.push_back(child->ToMojo());
+
+  return collection;
 }
 
 }  // namespace device
diff --git a/services/device/public/cpp/hid/hid_collection.h b/services/device/public/cpp/hid/hid_collection.h
index 4f964a0..32e9244 100644
--- a/services/device/public/cpp/hid/hid_collection.h
+++ b/services/device/public/cpp/hid/hid_collection.h
@@ -40,14 +40,17 @@
 
   uint32_t GetCollectionType() const { return collection_type_; }
 
-  // Returns true if there are one or more report IDs associated with this
+  // Return true if there are one or more report IDs associated with this
   // collection.
   bool HasReportId() const { return !report_ids_.empty(); }
 
-  // Returns information about the collection.
-  mojom::HidCollectionInfoPtr GetDetails(size_t* max_input_report_bits,
-                                         size_t* max_output_report_bits,
-                                         size_t* max_feature_report_bits);
+  // Compute the maximum size of any input, output, or feature report described
+  // by this collection.
+  void GetMaxReportSizes(size_t* max_input_report_bits,
+                         size_t* max_output_report_bits,
+                         size_t* max_feature_report_bits) const;
+
+  mojom::HidCollectionInfoPtr ToMojo() const;
 
   const HidCollection* GetParent() const { return parent_; }
 
diff --git a/services/device/public/cpp/hid/hid_item_state_table.h b/services/device/public/cpp/hid/hid_item_state_table.h
index 2f6ff56..c5f702e 100644
--- a/services/device/public/cpp/hid/hid_item_state_table.h
+++ b/services/device/public/cpp/hid/hid_item_state_table.h
@@ -46,7 +46,7 @@
 
     void Reset();
 
-    // Local items. See section 6.2.2.6 of the HID specifications.
+    // Local items. See section 6.2.2.8 of the HID specifications.
     std::vector<uint32_t> usages;
     uint32_t usage_minimum = 0;
     uint32_t usage_maximum = 0;
diff --git a/services/device/public/cpp/hid/hid_report_descriptor.cc b/services/device/public/cpp/hid/hid_report_descriptor.cc
index 22d3f06e..78e1bb33 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor.cc
+++ b/services/device/public/cpp/hid/hid_report_descriptor.cc
@@ -52,8 +52,8 @@
     size_t input_bits;
     size_t output_bits;
     size_t feature_bits;
-    top_level_collections->push_back(
-        collection->GetDetails(&input_bits, &output_bits, &feature_bits));
+    collection->GetMaxReportSizes(&input_bits, &output_bits, &feature_bits);
+    top_level_collections->push_back(collection->ToMojo());
     if (collection->HasReportId())
       *has_report_id = true;
     max_input_report_bits = std::max(max_input_report_bits, input_bits);
diff --git a/services/device/public/cpp/hid/hid_report_descriptor_item.cc b/services/device/public/cpp/hid/hid_report_descriptor_item.cc
index eb00a27..adcf403 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor_item.cc
+++ b/services/device/public/cpp/hid/hid_report_descriptor_item.cc
@@ -93,29 +93,4 @@
   return shortData_;
 }
 
-HidReportDescriptorItem::CollectionType
-HidReportDescriptorItem::GetCollectionTypeFromValue(uint32_t value) {
-  switch (value) {
-    case 0x00:
-      return kCollectionTypePhysical;
-    case 0x01:
-      return kCollectionTypeApplication;
-    case 0x02:
-      return kCollectionTypeLogical;
-    case 0x03:
-      return kCollectionTypeReport;
-    case 0x04:
-      return kCollectionTypeNamedArray;
-    case 0x05:
-      return kCollectionTypeUsageSwitch;
-    case 0x06:
-      return kCollectionTypeUsageModifier;
-    default:
-      break;
-  }
-  if (0x80 < value && value < 0xFF)
-    return kCollectionTypeVendor;
-  return kCollectionTypeReserved;
-}
-
 }  // namespace device
diff --git a/services/device/public/cpp/hid/hid_report_descriptor_item.h b/services/device/public/cpp/hid/hid_report_descriptor_item.h
index 0789ae8..9bb16a2a 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor_item.h
+++ b/services/device/public/cpp/hid/hid_report_descriptor_item.h
@@ -122,21 +122,6 @@
   static_assert(sizeof(ReportInfo) == sizeof(uint32_t),
                 "incorrect report info size");
 
-  // HID collection type.
-  // Can be retrieved from GetShortData()
-  // when item.tag() == HidReportDescriptorItem::kTagCollection
-  enum CollectionType {
-    kCollectionTypePhysical,
-    kCollectionTypeApplication,
-    kCollectionTypeLogical,
-    kCollectionTypeReport,
-    kCollectionTypeNamedArray,
-    kCollectionTypeUsageSwitch,
-    kCollectionTypeUsageModifier,
-    kCollectionTypeReserved,
-    kCollectionTypeVendor
-  };
-
  private:
   HidReportDescriptorItem(const uint8_t* bytes,
                           size_t size,
@@ -175,8 +160,6 @@
   // Size of this item in bytes, including the header.
   size_t GetSize() const;
 
-  static CollectionType GetCollectionTypeFromValue(uint32_t value);
-
  private:
   size_t GetHeaderSize() const;
   size_t payload_size() const { return payload_size_; }
diff --git a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
index 932cb65..dc831eef5 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
+++ b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
@@ -197,12 +197,10 @@
     HidReportDescriptorItem::kTagOutput;
 const HidReportDescriptorItem::Tag kFeature =
     HidReportDescriptorItem::kTagFeature;
-const HidReportDescriptorItem::CollectionType kCollectionTypeApplication =
-    HidReportDescriptorItem::kCollectionTypeApplication;
-const HidReportDescriptorItem::CollectionType kCollectionTypeLogical =
-    HidReportDescriptorItem::kCollectionTypeLogical;
-const HidReportDescriptorItem::CollectionType kCollectionTypePhysical =
-    kCollectionTypePhysical;
+const uint32_t kCollectionTypeApplication =
+    mojom::kHIDCollectionTypeApplication;
+const uint32_t kCollectionTypeLogical = mojom::kHIDCollectionTypeLogical;
+const uint32_t kCollectionTypePhysical = mojom::kHIDCollectionTypePhysical;
 
 }  // namespace
 
@@ -227,9 +225,7 @@
   }
 
   // Create a new collection and append it to |expected_collections_|.
-  HidCollection* AddTopCollection(
-      uint32_t usage,
-      HidReportDescriptorItem::CollectionType collection_type) {
+  HidCollection* AddTopCollection(uint32_t usage, uint32_t collection_type) {
     uint16_t usage_page = (usage >> kUsageIdSizeBits) & kUsageIdMask;
     usage = usage & kUsageIdMask;
     expected_collections_.push_back(std::make_unique<HidCollection>(
@@ -238,10 +234,9 @@
   }
 
   // Create a new collection as a child of |parent|.
-  HidCollection* AddChild(
-      HidCollection* parent,
-      uint32_t usage,
-      HidReportDescriptorItem::CollectionType collection_type) {
+  HidCollection* AddChild(HidCollection* parent,
+                          uint32_t usage,
+                          uint32_t collection_type) {
     uint16_t usage_page = (usage >> kUsageIdSizeBits) & kUsageIdMask;
     usage = usage & kUsageIdMask;
     parent->AddChildForTesting(std::make_unique<HidCollection>(
diff --git a/services/device/public/cpp/hid/hid_report_item.cc b/services/device/public/cpp/hid/hid_report_item.cc
index eeab22a..e0d7bb0 100644
--- a/services/device/public/cpp/hid/hid_report_item.cc
+++ b/services/device/public/cpp/hid/hid_report_item.cc
@@ -8,6 +8,16 @@
 
 namespace device {
 
+namespace {
+
+mojom::HidUsageAndPagePtr ConvertUsageToMojo(uint32_t usage) {
+  uint16_t usage_id = usage & 0xffff;
+  uint16_t usage_page = (usage >> 16) & 0xffff;
+  return mojom::HidUsageAndPage::New(usage_id, usage_page);
+}
+
+}  // namespace
+
 HidReportItem::HidReportItem(HidReportDescriptorItem::Tag tag,
                              uint32_t short_data,
                              const HidItemStateTable& state)
@@ -19,13 +29,7 @@
       global_(state.global_stack.empty()
                   ? HidItemStateTable::HidGlobalItemState()
                   : state.global_stack.back()),
-      is_range_(state.local.usage_minimum != state.local.usage_maximum),
-      has_strings_(state.local.string_index ||
-                   (state.local.string_minimum != state.local.string_maximum)),
-      has_designators_(
-          state.local.designator_index ||
-          (state.local.designator_minimum != state.local.designator_maximum)) {
-  global_.usage_page = mojom::kPageUndefined;
+      is_range_(state.local.usage_minimum != state.local.usage_maximum) {
   if (state.local.string_index) {
     local_.string_minimum = state.local.string_index;
     local_.string_maximum = state.local.string_index;
@@ -38,4 +42,42 @@
 
 HidReportItem::~HidReportItem() = default;
 
+mojom::HidReportItemPtr HidReportItem::ToMojo() const {
+  auto report_item = mojom::HidReportItem::New();
+  report_item->is_range = is_range_;
+
+  // Data associated with the Main item.
+  report_item->is_constant = report_info_.data_or_constant;
+  report_item->is_variable = report_info_.array_or_variable;
+  report_item->is_relative = report_info_.absolute_or_relative;
+  report_item->wrap = report_info_.wrap;
+  report_item->is_non_linear = report_info_.linear;
+  report_item->no_preferred_state = report_info_.preferred;
+  report_item->has_null_position = report_info_.null;
+  report_item->is_volatile = report_info_.is_volatile;
+  report_item->is_buffered_bytes = report_info_.bit_field_or_buffer;
+
+  // Local items.
+  for (const auto& item : local_.usages)
+    report_item->usages.push_back(ConvertUsageToMojo(item));
+  report_item->usage_minimum = ConvertUsageToMojo(local_.usage_minimum);
+  report_item->usage_maximum = ConvertUsageToMojo(local_.usage_maximum);
+  report_item->designator_minimum = local_.designator_minimum;
+  report_item->designator_maximum = local_.designator_maximum;
+  report_item->string_minimum = local_.string_minimum;
+  report_item->string_maximum = local_.string_maximum;
+
+  // Global items.
+  report_item->logical_minimum = global_.logical_minimum;
+  report_item->logical_maximum = global_.logical_maximum;
+  report_item->physical_minimum = global_.physical_minimum;
+  report_item->physical_maximum = global_.physical_maximum;
+  report_item->unit_exponent = global_.unit_exponent;
+  report_item->unit = global_.unit;
+  report_item->report_size = global_.report_size;
+  report_item->report_count = global_.report_count;
+
+  return report_item;
+}
+
 }  // namespace device
diff --git a/services/device/public/cpp/hid/hid_report_item.h b/services/device/public/cpp/hid/hid_report_item.h
index 44d1d9dd..52b363e 100644
--- a/services/device/public/cpp/hid/hid_report_item.h
+++ b/services/device/public/cpp/hid/hid_report_item.h
@@ -13,6 +13,7 @@
 
 #include "services/device/public/cpp/hid/hid_item_state_table.h"
 #include "services/device/public/cpp/hid/hid_report_descriptor_item.h"
+#include "services/device/public/mojom/hid.mojom.h"
 
 namespace device {
 
@@ -42,12 +43,6 @@
   // false if it defines a list of usages.
   bool IsRange() const { return is_range_; }
 
-  // Returns true if the usage or usage range has one or more strings.
-  bool HasStrings() const { return has_strings_; }
-
-  // Returns true if the usage or usage range has one or more designators.
-  bool HasDesignators() const { return has_designators_; }
-
   // Returns true if the report item is an absolute type, or false if it is a
   // relative type.
   bool IsAbsolute() const { return !report_info_.absolute_or_relative; }
@@ -103,6 +98,8 @@
   int32_t GetPhysicalMinimum() const { return global_.physical_minimum; }
   int32_t GetPhysicalMaximum() const { return global_.physical_maximum; }
 
+  mojom::HidReportItemPtr ToMojo() const;
+
  private:
   // The tag of the main item that generated this report item. Must be
   // kItemInput, kItemOutput, or kItemFeature.
@@ -122,12 +119,6 @@
   // If true, the usages for this item are defined by |local.usage_minimum| and
   // |local.usage_maximum|. If false, the usages are defomed by |local.usages|.
   bool is_range_;
-
-  // If true, one or more strings are associated with this item.
-  bool has_strings_;
-
-  // If true, one or more designators are associated with this item.
-  bool has_designators_;
 };
 
 }  // namespace device
diff --git a/services/device/public/mojom/hid.mojom b/services/device/public/mojom/hid.mojom
index 208a511..4e31ad0 100644
--- a/services/device/public/mojom/hid.mojom
+++ b/services/device/public/mojom/hid.mojom
@@ -42,10 +42,12 @@
 const uint16 kPageReservedPointOfSale = 0x8F;
 const uint16 kPageCameraControl = 0x90;
 const uint16 kPageArcade = 0x91;
+const uint16 kPageFido = 0xF1D0;
 const uint16 kPageVendor = 0xFF00;
 const uint16 kPageMediaCenter = 0xFFBC;
 
-// These usage enumerations are derived from the HID Usage Tables v1.11 spec.
+// These usage enumerations are derived from the HID Usage Tables v1.12 spec.
+// https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
 const uint16 kGenericDesktopUndefined = 0x00;
 const uint16 kGenericDesktopPointer = 0x01;
 const uint16 kGenericDesktopMouse = 0x02;
@@ -116,18 +118,137 @@
 const uint16 kGenericDesktopSystemDisplayToggle = 0xb5;
 const uint16 kGenericDesktopSystemDisplaySwap = 0xb6;
 
+// These collection types are defined in section 6.2.2.6 of the Device Class
+// Definition for HID.
+// https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+const uint32 kHIDCollectionTypePhysical = 0x00;
+const uint32 kHIDCollectionTypeApplication = 0x01;
+const uint32 kHIDCollectionTypeLogical = 0x02;
+const uint32 kHIDCollectionTypeReport = 0x03;
+const uint32 kHIDCollectionTypeNamedArray = 0x04;
+const uint32 kHIDCollectionTypeUsageSwitch = 0x05;
+const uint32 kHIDCollectionTypeUsageModifier = 0x06;
+const uint32 kHIDCollectionTypeVendorMin = 0x80;
+const uint32 kHIDCollectionTypeVendorMax = 0xff;
+
 struct HidUsageAndPage {
   uint16 usage;
   uint16 usage_page;
 };
 
+struct HidReportItem {
+  // True if the usages for this item are defined by |usage_minimum| and
+  // |usage_maximum|. False if the usages for this item are defined by |usages|.
+  bool is_range;
+
+  // Data associated with the Main item. See section 6.2.2.5 of the Device Class
+  // Definition for HID.
+  // https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+  bool is_constant;         // Constant (true) or Data (false).
+  bool is_variable;         // Variable (true) or Array (false).
+  bool is_relative;         // Relative (true) or Absolute (false).
+  bool wrap;                // Wrap (true) or No Wrap (false).
+  bool is_non_linear;       // Non Linear (true) or Linear (false).
+  bool no_preferred_state;  // No Preferred (true) or Preferred State (false).
+  bool has_null_position;   // Null state (true) or No Null position (false).
+  bool is_volatile;         // Volatile (true) or Non Volatile (false).
+  bool is_buffered_bytes;   // Buffered Bytes (true) or Bit Field (false).
+
+  // Local items. See section 6.2.2.8 of the Device Class Definition for HID.
+  // https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+
+  // If |is_range| is false, usages for this item are listed in |usages| in the
+  // order they were encountered in the report descriptor.
+  array<HidUsageAndPage> usages;
+
+  // If |is_range| is true, usages for this item are assigned from a range of
+  // usages starting at |usage_minimum| and incrementing until |usage_maximum|.
+  // If this item is a Variable and |report_count| is larger than the number of
+  // usages in this range, all remaining fields are also assigned
+  // |usage_maximum|.
+  HidUsageAndPage usage_minimum;
+  HidUsageAndPage usage_maximum;
+
+  // If this item has one or more entries in the Physical descriptor table,
+  // |designator_minimum| and |designator_maximum| are set to the minimum and
+  // maximum indices of these entries. If the item has no designators, both are
+  // set to zero. A designator describes the body part intended to be used with
+  // a particular control.
+  uint32 designator_minimum;
+  uint32 designator_maximum;
+
+  // If this item has one or more entries in the String descriptor table,
+  // |string_minimum| and |string_maximum| are set to the minimum and maximum
+  // indices of these entries. If the item has no strings, both are set to zero.
+  // The String descriptor contains a list of text strings for the device.
+  uint32 string_minimum;
+  uint32 string_maximum;
+
+  // Global items. See section 6.2.2.7 of the Device Class Definition for HID.
+  // https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+
+  // |logical_minimum| and |logical_maximum| define the extent of valid data
+  // values for the item in logical units. If |has_null_position| is true,
+  // values outside this range are interpreted as null input.
+  int32 logical_minimum;
+  int32 logical_maximum;
+
+  // |physical_minimum| and |physical_maximum| define the extent of valid data
+  // values after applying units to the logical extents.
+  int32 physical_minimum;
+  int32 physical_maximum;
+
+  // The value of the unit exponent in base 10. Values between 0x0 and 0x7
+  // represent positive exponents 0 to 7, values between 0x8 and 0xF represent
+  // nevative exponents -8 to -1. Bits [4:31] are reserved and should be set to
+  // zero.
+  uint32 unit_exponent;
+
+  // The units to apply to this item. The |unit| value is coded as seven 4-bit
+  // fields that define the unit system and the exponents on units of length,
+  // mass, time, temperature, current, and luminous intensity. Bits [28:31] are
+  // reserved and should be set to zero.
+  uint32 unit;
+
+  // A single report item may define multiple same-sized fields within a report.
+  // |report_size| and |report_count| define the size of one field (in bits) and
+  // the number of fields within the item. The total size of this item in bits
+  // is equal to the product of these values.
+  uint32 report_size;
+  uint32 report_count;
+};
+
+struct HidReportDescription {
+  // Report ID associated with this report, or zero if the device does not use
+  // report IDs.
+  uint8 report_id;
+
+  // The sequence of report items that describe this report.
+  array<HidReportItem> items;
+};
+
 struct HidCollectionInfo {
   // Collection's usage ID.
   HidUsageAndPage usage;
 
-  // HID report IDs which belong to this collection or to its
-  // embedded collections.
-  array<int32> report_ids;
+  // HID report IDs which belong to this collection or to its embedded
+  // collections, in the order they appear in the report descriptor.
+  array<uint8> report_ids;
+
+  // Collection type.
+  uint32 collection_type;
+
+  // Reports described in the report descriptor.
+  array<HidReportDescription> input_reports;
+  array<HidReportDescription> output_reports;
+  array<HidReportDescription> feature_reports;
+
+  // The children of this collection in the order they appear in the report
+  // descriptor. In child collections, the reports described in the
+  // |input_reports|, |output_reports|, and |feature_reports| members include
+  // only the subsequence of report items from the parent collection that appear
+  // within the child collection.
+  array<HidCollectionInfo> children;
 };
 
 struct HidDeviceInfo {
diff --git a/services/image_annotation/BUILD.gn b/services/image_annotation/BUILD.gn
index 9ffdee9..8b28b73 100644
--- a/services/image_annotation/BUILD.gn
+++ b/services/image_annotation/BUILD.gn
@@ -15,8 +15,10 @@
     "//components/google/core/common",
     "//mojo/public/cpp/bindings",
     "//net",
+    "//services/data_decoder/public/mojom",
     "//services/image_annotation/public/mojom",
     "//services/network/public/cpp",
+    "//services/service_manager/public/cpp",
     "//url",
   ]
 }
@@ -51,10 +53,14 @@
     "//base/test:test_support",
     "//mojo/public/cpp/bindings",
     "//net",
+    "//services/data_decoder/public/cpp:test_support",
+    "//services/data_decoder/public/mojom",
     "//services/image_annotation/public/cpp",
     "//services/image_annotation/public/mojom",
     "//services/network:test_support",
     "//services/network/public/cpp",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/cpp/test:test_support",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
diff --git a/services/image_annotation/DEPS b/services/image_annotation/DEPS
index af92bdd5..a6a59aaf1 100644
--- a/services/image_annotation/DEPS
+++ b/services/image_annotation/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+components/google",
   "+net",
+  "+services/data_decoder",
   "+services/network",
   "+third_party/skia",
   "+ui/gfx",
diff --git a/services/image_annotation/annotator.cc b/services/image_annotation/annotator.cc
index fb24764..bf33e8410 100644
--- a/services/image_annotation/annotator.cc
+++ b/services/image_annotation/annotator.cc
@@ -11,13 +11,14 @@
 #include "base/base64.h"
 #include "base/bind.h"
 #include "base/feature_list.h"
-#include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/location.h"
 #include "base/stl_util.h"
 #include "components/google/core/common/google_util.h"
 #include "net/base/load_flags.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "url/gurl.h"
 
 namespace image_annotation {
@@ -153,18 +154,13 @@
 
 // Attempts to extract annotation results from the server response, returning a
 // map from each source ID to its annotations (if successfully extracted).
-std::map<std::string, mojom::AnnotateImageResultPtr> ParseJsonResponse(
-    const std::string* const json_response,
+std::map<std::string, mojom::AnnotateImageResultPtr> UnpackJsonResponse(
+    const base::Value& json_data,
     const double min_ocr_confidence) {
-  if (!json_response)
+  if (!json_data.is_dict())
     return {};
 
-  const base::Optional<base::Value> response =
-      base::JSONReader::Read(*json_response);
-  if (!response.has_value() || !response->is_dict())
-    return {};
-
-  const base::Value* const results = response->FindKey("results");
+  const base::Value* const results = json_data.FindKey("results");
   if (!results || !results->is_list())
     return {};
 
@@ -240,8 +236,10 @@
     const base::TimeDelta throttle,
     const int batch_size,
     const double min_ocr_confidence,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    service_manager::Connector* const connector)
     : url_loader_factory_(std::move(url_loader_factory)),
+      connector_(connector),
       http_request_timer_(
           FROM_HERE,
           throttle,
@@ -250,7 +248,9 @@
       server_url_(std::move(server_url)),
       api_key_(std::move(api_key)),
       batch_size_(batch_size),
-      min_ocr_confidence_(min_ocr_confidence) {}
+      min_ocr_confidence_(min_ocr_confidence) {
+  DCHECK(connector_);
+}
 
 Annotator::~Annotator() {}
 
@@ -465,10 +465,37 @@
     const std::unique_ptr<std::string> json_response) {
   http_requests_.erase(http_request_it);
 
-  // Extract annotation results for each source ID with valid results.
-  const std::map<std::string, mojom::AnnotateImageResultPtr> results =
-      ParseJsonResponse(json_response.get(), min_ocr_confidence_);
+  if (!json_response) {
+    DVLOG(1) << "HTTP request to image annotation server failed.";
+    ProcessResults(source_ids, {});
+    return;
+  }
 
+  // Send JSON string to a dedicated service for safe parsing.
+  GetJsonParser().Parse(*json_response,
+                        base::BindOnce(&Annotator::OnResponseJsonParsed,
+                                       base::Unretained(this), source_ids));
+}
+
+void Annotator::OnResponseJsonParsed(
+    const std::set<std::string>& source_ids,
+    const base::Optional<base::Value> json_data,
+    const base::Optional<std::string>& error) {
+  // Extract annotation results for each source ID with valid results.
+  std::map<std::string, mojom::AnnotateImageResultPtr> results;
+  if (!json_data.has_value() || error.has_value()) {
+    DVLOG(1) << "Parsing server response JSON failed with error: "
+             << error.value_or("No reason reported.");
+    ProcessResults(source_ids, {});
+  } else {
+    ProcessResults(source_ids,
+                   UnpackJsonResponse(*json_data, min_ocr_confidence_));
+  }
+}
+
+void Annotator::ProcessResults(
+    const std::set<std::string>& source_ids,
+    const std::map<std::string, mojom::AnnotateImageResultPtr>& results) {
   // Process each source ID for which we expect to have results.
   for (const std::string& source_id : source_ids) {
     pending_source_ids_.erase(source_id);
@@ -502,6 +529,18 @@
   }
 }
 
+data_decoder::mojom::JsonParser& Annotator::GetJsonParser() {
+  if (!json_parser_) {
+    connector_->BindInterface(data_decoder::mojom::kServiceName,
+                              mojo::MakeRequest(&json_parser_));
+    json_parser_.set_connection_error_handler(base::BindOnce(
+        [](Annotator* const annotator) { annotator->json_parser_.reset(); },
+        base::Unretained(this)));
+  }
+
+  return *json_parser_;
+}
+
 void Annotator::RemoveRequestInfo(
     const std::string& source_id,
     const RequestInfoList::iterator request_info_it,
diff --git a/services/image_annotation/annotator.h b/services/image_annotation/annotator.h
index a102373..bf4df32 100644
--- a/services/image_annotation/annotator.h
+++ b/services/image_annotation/annotator.h
@@ -18,11 +18,16 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/data_decoder/public/mojom/json_parser.mojom.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "url/gurl.h"
 
+namespace service_manager {
+class Connector;
+}  // namespace service_manager
+
 namespace image_annotation {
 
 // The annotator communicates with the external image annotation server to
@@ -61,7 +66,8 @@
             base::TimeDelta throttle,
             int batch_size,
             double min_ocr_confidence,
-            scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+            scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+            service_manager::Connector* connector);
   ~Annotator() override;
 
   // Start providing behavior for the given Mojo request.
@@ -101,6 +107,10 @@
       HttpRequestQueue::iterator begin_it,
       HttpRequestQueue::iterator end_it);
 
+  // Create or reuse a connection to the data decoder service for safe JSON
+  // parsing.
+  data_decoder::mojom::JsonParser& GetJsonParser();
+
   // Removes the given request, reassigning local processing if its associated
   // image processor had some ongoing.
   void RemoveRequestInfo(const std::string& source_id,
@@ -123,6 +133,17 @@
                                 UrlLoaderList::iterator http_request_it,
                                 std::unique_ptr<std::string> json_response);
 
+  // Called when the data decoder service provides parsed JSON data for a server
+  // response.
+  void OnResponseJsonParsed(const std::set<std::string>& source_ids,
+                            base::Optional<base::Value> json_data,
+                            const base::Optional<std::string>& error);
+
+  // Adds the given results to the cache (if successful) and notifies clients.
+  void ProcessResults(
+      const std::set<std::string>& source_ids,
+      const std::map<std::string, mojom::AnnotateImageResultPtr>& results);
+
   // Maps from source ID to previously-obtained annotation results.
   // TODO(crbug.com/916420): periodically clear entries from this cache.
   std::map<std::string, mojom::AnnotateImageResultPtr> cached_results_;
@@ -150,8 +171,13 @@
 
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 
+  service_manager::Connector* const connector_;
+
   mojo::BindingSet<mojom::Annotator> bindings_;
 
+  // Should not be used directly; GetJsonParser() should be called instead.
+  data_decoder::mojom::JsonParserPtr json_parser_;
+
   // A timer used to throttle HTTP request frequency.
   base::RepeatingTimer http_request_timer_;
 
diff --git a/services/image_annotation/annotator_unittest.cc b/services/image_annotation/annotator_unittest.cc
index 48175ec..b665603 100644
--- a/services/image_annotation/annotator_unittest.cc
+++ b/services/image_annotation/annotator_unittest.cc
@@ -14,10 +14,15 @@
 #include "base/time/time.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "net/http/http_status_code.h"
+#include "services/data_decoder/public/cpp/test_data_decoder_service.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
+#include "services/data_decoder/public/mojom/json_parser.mojom.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader.mojom-shared.h"
 #include "services/network/test/test_url_loader_factory.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/mojom/service.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -191,6 +196,8 @@
 // An image processor that holds and exposes the callbacks it is passed.
 class TestImageProcessor : public mojom::ImageProcessor {
  public:
+  TestImageProcessor() = default;
+
   mojom::ImageProcessorPtr GetPtr() {
     mojom::ImageProcessorPtr ptr;
     bindings_.AddBinding(this, mojo::MakeRequest(&ptr));
@@ -212,6 +219,8 @@
   std::vector<GetJpgImageDataCallback> callbacks_;
 
   mojo::BindingSet<mojom::ImageProcessor> bindings_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestImageProcessor);
 };
 
 // A class that supports test URL loading for the "server" use case: where
@@ -298,6 +307,8 @@
   const std::string server_url_prefix_;
   network::TestURLLoaderFactory loader_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_loader_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestServerURLLoaderFactory);
 };
 
 // Returns a "canonically" formatted version of a JSON string by parsing and
@@ -335,11 +346,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
   TestImageProcessor processor;
 
   // First call performs original image annotation.
@@ -401,11 +413,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -446,11 +459,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -492,11 +506,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor;
   base::Optional<mojom::AnnotateImageError> error;
@@ -538,11 +553,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -601,11 +617,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -663,11 +680,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 3 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      3 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor[3];
   base::Optional<mojom::AnnotateImageError> error[3];
@@ -724,11 +742,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 3 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      3 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor[2];
   base::Optional<mojom::AnnotateImageError> error[2];
@@ -829,11 +848,12 @@
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
+  data_decoder::TestDataDecoderService test_dd_service;
 
-  Annotator annotator(GURL(kTestServerUrl), std::string() /* api_key */,
-                      kThrottle, 1 /* batch_size */,
-                      1.0 /* min_ocr_confidence */,
-                      test_url_factory.AsSharedURLLoaderFactory());
+  Annotator annotator(
+      GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
+      1 /* batch_size */, 1.0 /* min_ocr_confidence */,
+      test_url_factory.AsSharedURLLoaderFactory(), test_dd_service.connector());
 
   TestImageProcessor processor[4];
   base::Optional<mojom::AnnotateImageError> error[4];
@@ -920,6 +940,7 @@
 TEST(AnnotatorTest, ApiKey) {
   base::test::ScopedTaskEnvironment test_task_env(
       base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME);
+  data_decoder::TestDataDecoderService test_dd_service;
 
   // A call to a secure Google-owner server URL should include the specified API
   // key.
@@ -929,7 +950,8 @@
 
     Annotator annotator(GURL(kTestServerUrl), "my_api_key", kThrottle,
                         1 /* batch_size */, 1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory());
+                        test_url_factory.AsSharedURLLoaderFactory(),
+                        test_dd_service.connector());
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, processor.GetPtr(), base::DoNothing());
@@ -961,7 +983,8 @@
     Annotator annotator(GURL("http://ia-pa.googleapis.com/v1/annotation"),
                         "my_api_key", kThrottle, 1 /* batch_size */,
                         1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory());
+                        test_url_factory.AsSharedURLLoaderFactory(),
+                        test_dd_service.connector());
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, processor.GetPtr(), base::DoNothing());
@@ -990,7 +1013,8 @@
     Annotator annotator(GURL("https://datascraper.com/annotation"),
                         "my_api_key", kThrottle, 1 /* batch_size */,
                         1.0 /* min_ocr_confidence */,
-                        test_url_factory.AsSharedURLLoaderFactory());
+                        test_url_factory.AsSharedURLLoaderFactory(),
+                        test_dd_service.connector());
     TestImageProcessor processor;
 
     annotator.AnnotateImage(kImage1Url, processor.GetPtr(), base::DoNothing());
diff --git a/services/image_annotation/image_annotation_service.cc b/services/image_annotation/image_annotation_service.cc
index 45d015b..eb51a80 100644
--- a/services/image_annotation/image_annotation_service.cc
+++ b/services/image_annotation/image_annotation_service.cc
@@ -29,7 +29,8 @@
                  base::TimeDelta::FromMilliseconds(kThrottleMs.Get()),
                  kBatchSize.Get(),
                  kMinOcrConfidence.Get(),
-                 shared_url_loader_factory) {}
+                 shared_url_loader_factory,
+                 service_binding_.GetConnector()) {}
 
 ImageAnnotationService::~ImageAnnotationService() = default;
 
diff --git a/services/image_annotation/public/cpp/BUILD.gn b/services/image_annotation/public/cpp/BUILD.gn
index 0af7464..9756393a 100644
--- a/services/image_annotation/public/cpp/BUILD.gn
+++ b/services/image_annotation/public/cpp/BUILD.gn
@@ -24,6 +24,7 @@
 
   public_deps = [
     "//base",
+    "//services/data_decoder/public/mojom:constants",
     "//services/image_annotation/public/mojom",
     "//services/service_manager/public/cpp",
   ]
diff --git a/services/image_annotation/public/cpp/manifest.cc b/services/image_annotation/public/cpp/manifest.cc
index 808f5de..101f67d 100644
--- a/services/image_annotation/public/cpp/manifest.cc
+++ b/services/image_annotation/public/cpp/manifest.cc
@@ -5,6 +5,7 @@
 #include "services/image_annotation/public/cpp/manifest.h"
 
 #include "base/no_destructor.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/image_annotation/public/mojom/constants.mojom.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/service_manager/public/cpp/manifest_builder.h"
@@ -19,6 +20,7 @@
           .ExposeCapability(
               mojom::kAnnotationCapability,
               service_manager::Manifest::InterfaceList<mojom::Annotator>())
+          .RequireCapability(data_decoder::mojom::kServiceName, "json_parser")
           .Build()};
   return *manifest;
 }
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 7d2455b6..a867be6 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -1052,6 +1052,10 @@
   report_raw_headers_ = want_raw_headers_ && allow;
 }
 
+uint32_t URLLoader::GetResourceType() const {
+  return resource_type_;
+}
+
 // static
 URLLoader* URLLoader::ForRequest(const net::URLRequest& request) {
   auto* pointer =
diff --git a/services/network/url_loader.h b/services/network/url_loader.h
index 8ab338a..796893b 100644
--- a/services/network/url_loader.h
+++ b/services/network/url_loader.h
@@ -114,6 +114,7 @@
 
   uint32_t GetRenderFrameId() const;
   uint32_t GetProcessId() const;
+  uint32_t GetResourceType() const;
 
   const net::HttpRequestHeaders& custom_proxy_pre_cache_headers() const {
     return custom_proxy_pre_cache_headers_;
diff --git a/services/resource_coordinator/OWNERS b/services/resource_coordinator/OWNERS
index c55119f..021ff20 100644
--- a/services/resource_coordinator/OWNERS
+++ b/services/resource_coordinator/OWNERS
@@ -1,5 +1,7 @@
 chrisha@chromium.org
 fdoray@chromium.org
+
+# Emeritus:
 haraken@chromium.org
 lpy@chromium.org
 oysteine@chromium.org
diff --git a/services/tracing/public/cpp/base_agent.cc b/services/tracing/public/cpp/base_agent.cc
index f10dd41..116a8f0 100644
--- a/services/tracing/public/cpp/base_agent.cc
+++ b/services/tracing/public/cpp/base_agent.cc
@@ -56,4 +56,8 @@
   std::move(callback).Run();
 }
 
+bool BaseAgent::IsBoundForTesting() const {
+  return binding_.is_bound();
+}
+
 }  // namespace tracing
diff --git a/services/tracing/public/cpp/base_agent.h b/services/tracing/public/cpp/base_agent.h
index 0c34895..6fd6c43 100644
--- a/services/tracing/public/cpp/base_agent.h
+++ b/services/tracing/public/cpp/base_agent.h
@@ -30,6 +30,8 @@
             mojom::TraceDataType type,
             base::ProcessId pid);
 
+  bool IsBoundForTesting() const;
+
  private:
   void Disconnect();
 
diff --git a/services/tracing/public/cpp/perfetto/producer_client.cc b/services/tracing/public/cpp/perfetto/producer_client.cc
index 862a215..3ee4268 100644
--- a/services/tracing/public/cpp/perfetto/producer_client.cc
+++ b/services/tracing/public/cpp/perfetto/producer_client.cc
@@ -79,6 +79,7 @@
 
 // static
 void ProducerClient::ResetTaskRunnerForTesting() {
+  DETACH_FROM_SEQUENCE(ProducerClient::Get()->sequence_checker_);
   GetPerfettoTaskRunner()->ResetTaskRunnerForTesting(CreateTaskRunner());
 }
 
diff --git a/services/tracing/public/cpp/trace_event_agent.cc b/services/tracing/public/cpp/trace_event_agent.cc
index 3625b09c..faca13d1 100644
--- a/services/tracing/public/cpp/trace_event_agent.cc
+++ b/services/tracing/public/cpp/trace_event_agent.cc
@@ -80,7 +80,7 @@
 void TraceEventAgent::StartTracing(const std::string& config,
                                    base::TimeTicks coordinator_time,
                                    StartTracingCallback callback) {
-  DCHECK(!TracingUsesPerfettoBackend());
+  DCHECK(!IsBoundForTesting() || !TracingUsesPerfettoBackend());
   DCHECK(!recorder_);
   DCHECK(!tracing_enabled_callback_);
 #if defined(__native_client__)
@@ -100,7 +100,7 @@
 }
 
 void TraceEventAgent::StopAndFlush(mojom::RecorderPtr recorder) {
-  DCHECK(!TracingUsesPerfettoBackend());
+  DCHECK(!IsBoundForTesting() || !TracingUsesPerfettoBackend());
   DCHECK(!recorder_);
 
   recorder_ = std::move(recorder);
@@ -119,7 +119,7 @@
 
 void TraceEventAgent::RequestBufferStatus(
     RequestBufferStatusCallback callback) {
-  DCHECK(!TracingUsesPerfettoBackend());
+  DCHECK(!IsBoundForTesting() || !TracingUsesPerfettoBackend());
   base::trace_event::TraceLogStatus status =
       base::trace_event::TraceLog::GetInstance()->GetStatus();
   std::move(callback).Run(status.event_capacity, status.event_count);
diff --git a/services/tracing/public/cpp/trace_event_agent_unittest.cc b/services/tracing/public/cpp/trace_event_agent_unittest.cc
index 9749aac3..8578b7232 100644
--- a/services/tracing/public/cpp/trace_event_agent_unittest.cc
+++ b/services/tracing/public/cpp/trace_event_agent_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/trace_log.h"
 #include "base/values.h"
+#include "services/tracing/public/cpp/perfetto/producer_client.h"
 #include "services/tracing/public/mojom/tracing.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -82,6 +83,8 @@
 
 class TraceEventAgentTest : public testing::Test {
  public:
+  void SetUp() override { ProducerClient::ResetTaskRunnerForTesting(); }
+
   void TearDown() override {
     base::trace_event::TraceLog::GetInstance()->SetDisabled();
     recorder_.reset();
diff --git a/services/ws/public/cpp/gpu/context_provider_command_buffer.cc b/services/ws/public/cpp/gpu/context_provider_command_buffer.cc
index 5981256a..3efc35c 100644
--- a/services/ws/public/cpp/gpu/context_provider_command_buffer.cc
+++ b/services/ws/public/cpp/gpu/context_provider_command_buffer.cc
@@ -20,6 +20,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager.h"
+#include "build/build_config.h"
 #include "components/viz/common/gpu/context_cache_controller.h"
 #include "gpu/command_buffer/client/gles2_cmd_helper.h"
 #include "gpu/command_buffer/client/gles2_trace_implementation.h"
@@ -145,10 +146,18 @@
     return bind_result_;
   }
 
-  bool allow_raster_decoder =
-      !command_buffer_->channel()->gpu_info().passthrough_cmd_decoder ||
+  // TODO(enne): remove the kEnablePassthroughRasterDecoder flag and
+  // assume it is always available.
+  bool enable_passthrough_raster_decoder =
       base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kEnablePassthroughRasterDecoder);
+#if defined(OS_WIN)
+  enable_passthrough_raster_decoder = true;
+#endif
+
+  bool allow_raster_decoder =
+      !command_buffer_->channel()->gpu_info().passthrough_cmd_decoder ||
+      enable_passthrough_raster_decoder;
   if (attributes_.context_type == gpu::CONTEXT_TYPE_WEBGPU) {
     DCHECK(!attributes_.enable_raster_interface);
     DCHECK(!attributes_.enable_gles2_interface);
diff --git a/storage/DEPS b/storage/DEPS
index 83e7442..93263a1 100644
--- a/storage/DEPS
+++ b/storage/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/services/filesystem",
+  "+crypto",
   "+mojo",
   "+net",
   "+services/network/public/cpp",
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index f00fd1e..34b94bb 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -185,6 +185,8 @@
     "fileapi/watcher_manager.h",
     "quota/client_usage_tracker.cc",
     "quota/client_usage_tracker.h",
+    "quota/padding_key.cc",
+    "quota/padding_key.h",
     "quota/quota_callbacks.h",
     "quota/quota_client.cc",
     "quota/quota_client.h",
diff --git a/storage/browser/quota/padding_key.cc b/storage/browser/quota/padding_key.cc
new file mode 100644
index 0000000..03042a6
--- /dev/null
+++ b/storage/browser/quota/padding_key.cc
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "storage/browser/quota/padding_key.h"
+
+#include "base/no_destructor.h"
+
+using crypto::SymmetricKey;
+
+namespace storage {
+
+namespace {
+
+const SymmetricKey::Algorithm kPaddingKeyAlgorithm = SymmetricKey::AES;
+
+std::unique_ptr<SymmetricKey>* GetPaddingKey() {
+  static base::NoDestructor<std::unique_ptr<SymmetricKey>> s_padding_key([] {
+    return SymmetricKey::GenerateRandomKey(kPaddingKeyAlgorithm, 128);
+  }());
+  return s_padding_key.get();
+}
+
+}  // namespace
+
+std::unique_ptr<SymmetricKey> CopyDefaultPaddingKey() {
+  return SymmetricKey::Import(kPaddingKeyAlgorithm, (*GetPaddingKey())->key());
+}
+
+std::unique_ptr<SymmetricKey> DeserializePaddingKey(
+    const std::string& raw_key) {
+  return SymmetricKey::Import(kPaddingKeyAlgorithm, raw_key);
+}
+
+std::string SerializeDefaultPaddingKey() {
+  return (*GetPaddingKey())->key();
+}
+
+void ResetPaddingKeyForTesting() {
+  *GetPaddingKey() = SymmetricKey::GenerateRandomKey(kPaddingKeyAlgorithm, 128);
+}
+
+}  // namespace storage
diff --git a/storage/browser/quota/padding_key.h b/storage/browser/quota/padding_key.h
new file mode 100644
index 0000000..01dcde82
--- /dev/null
+++ b/storage/browser/quota/padding_key.h
@@ -0,0 +1,48 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef STORAGE_BROWSER_QUOTA_PADDING_KEY_H_
+#define STORAGE_BROWSER_QUOTA_PADDING_KEY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/component_export.h"
+#include "crypto/symmetric_key.h"
+
+namespace storage {
+
+// Return a copy of the default key used to calculate padding sizes.
+//
+// The default padding key is a singleton object whose value is randomly
+// generated the first time it is requested on every browser startup. When a
+// cache does not have a padding key, it is assigned the current default key.
+COMPONENT_EXPORT(STORAGE_BROWSER)
+std::unique_ptr<crypto::SymmetricKey> CopyDefaultPaddingKey();
+
+// Build a key whose value is the given string.
+//
+// May return null if deserializing fails (e.g. if the raw key is the wrong
+// size).
+COMPONENT_EXPORT(STORAGE_BROWSER)
+std::unique_ptr<crypto::SymmetricKey> DeserializePaddingKey(
+    const std::string& raw_key);
+
+// Get the raw value of the default padding key.
+//
+// Each cache stores the raw value of the key that should be used when
+// calculating its padding size.
+COMPONENT_EXPORT(STORAGE_BROWSER)
+std::string SerializeDefaultPaddingKey();
+
+// Reset the default key to a random value.
+//
+// Simulating a key change across a browser restart lets us test that padding
+// calculations are using the appropriate key.
+COMPONENT_EXPORT(STORAGE_BROWSER)
+void ResetPaddingKeyForTesting();
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_QUOTA_PADDING_KEY_H_
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 6b5d68b..2e3de5c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2599,7 +2599,7 @@
             ]
         }
     ],
-    "MediaCapabilitiesParams": [
+    "MediaCapabilitiesWithParameters": [
         {
             "platforms": [
                 "android",
@@ -2610,7 +2610,7 @@
             ],
             "experiments": [
                 {
-                    "name": "DBWindow5000Frames_20190211"
+                    "name": "DBWindow5000Frames_20190225"
                 }
             ]
         }
@@ -3738,21 +3738,6 @@
             ]
         }
     ],
-    "PreviewsDisallowedOnReloads": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "PreviewsDisallowedOnReloads"
-                    ]
-                }
-            ]
-        }
-    ],
     "PreviewsLitePageRedirect": [
         {
             "platforms": [
@@ -3913,97 +3898,17 @@
         {
             "platforms": [
                 "android",
-                "ios",
+                "chromeos",
                 "linux",
                 "mac",
                 "windows"
             ],
             "experiments": [
                 {
-                    "name": "Enabled_Pool_TwoThirds_Origin_OneFifth",
+                    "name": "Enabled_Pool_Four_Fifths_Origin_ThreeQuarters",
                     "params": {
-                        "PerHostPortion": "0.2",
-                        "PoolSizeRatio": "0.6666666666666666"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_ThreeQuarters_Origin_OneFifth",
-                    "params": {
-                        "PerHostPortion": "0.2",
-                        "PoolSizeRatio": "0.75"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_Full_Origin_OneFifth",
-                    "params": {
-                        "PerHostPortion": "0.2",
-                        "PoolSizeRatio": "1.0"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_TwoThirds_Origin_OneHalf",
-                    "params": {
-                        "PerHostPortion": "0.5",
-                        "PoolSizeRatio": "0.6666666666666666"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_ThreeQuarters_Origin_OneHalf",
-                    "params": {
-                        "PerHostPortion": "0.5",
-                        "PoolSizeRatio": "0.75"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_Full_Origin_OneHalf",
-                    "params": {
-                        "PerHostPortion": "0.5",
-                        "PoolSizeRatio": "1.0"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_TwoThirds_Origin_TwoThirds",
-                    "params": {
-                        "PerHostPortion": "0.6666666666666666",
-                        "PoolSizeRatio": "0.6666666666666666"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_ThreeQuarters_Origin_TwoThirds",
-                    "params": {
-                        "PerHostPortion": "0.6666666666666666",
-                        "PoolSizeRatio": "0.75"
-                    },
-                    "enable_features": [
-                        "QuotaExpandPoolSize"
-                    ]
-                },
-                {
-                    "name": "Enabled_Pool_Full_Origin_TwoThirds",
-                    "params": {
-                        "PerHostPortion": "0.6666666666666666",
-                        "PoolSizeRatio": "1.0"
+                        "PerHostRatio": "0.75",
+                        "PoolSizeRatio": "0.8"
                     },
                     "enable_features": [
                         "QuotaExpandPoolSize"
diff --git a/third_party/blink/public/platform/modules/idle/idle_manager.mojom b/third_party/blink/public/platform/modules/idle/idle_manager.mojom
index e5ee660..09118ab00 100644
--- a/third_party/blink/public/platform/modules/idle/idle_manager.mojom
+++ b/third_party/blink/public/platform/modules/idle/idle_manager.mojom
@@ -4,6 +4,8 @@
 
 module blink.mojom;
 
+import "mojo/public/mojom/base/time.mojom";
+
 // Implementation of the proposed "Idle Detection API".
 //
 // Proposal: https://github.com/inexorabletash/idle-detection
@@ -32,5 +34,6 @@
   // initial state. It will be notified by calls to Update() per the threshold
   // registered for this instance. It can be unregistered by simply closing
   // the pipe.
-  AddMonitor(uint32 threshold, IdleMonitor monitor) => (IdleState state);
+  AddMonitor(mojo_base.mojom.TimeDelta threshold, IdleMonitor monitor)
+      => (IdleState state);
 };
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 5cd2673..91c4fd9a 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -104,7 +104,6 @@
   BLINK_PLATFORM_EXPORT static void EnableFileSystem(bool);
   BLINK_PLATFORM_EXPORT static void EnableFirstContentfulPaintPlusPlus(bool);
   BLINK_PLATFORM_EXPORT static void EnableForceTallerSelectPopup(bool);
-  BLINK_PLATFORM_EXPORT static void EnableGamepadVibration(bool);
   BLINK_PLATFORM_EXPORT static void EnableGenericSensor(bool);
   BLINK_PLATFORM_EXPORT static void EnableGenericSensorExtraClasses(bool);
   BLINK_PLATFORM_EXPORT static void EnableHeapCompaction(bool);
diff --git a/third_party/blink/renderer/controller/oom_intervention_impl.cc b/third_party/blink/renderer/controller/oom_intervention_impl.cc
index c98fd20b..d49b9f8 100644
--- a/third_party/blink/renderer/controller/oom_intervention_impl.cc
+++ b/third_party/blink/renderer/controller/oom_intervention_impl.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/controller/oom_intervention_impl.h"
 
 #include "base/bind.h"
+#include "base/debug/crash_logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
@@ -18,6 +19,46 @@
 
 namespace blink {
 
+namespace {
+enum class OomInterventionState {
+  // Initial value for a variable.
+  None,
+  // Before the intervention has been triggered.
+  Before,
+  // While the intervention is active.
+  During,
+  // After the intervention has triggered at least once.
+  After
+};
+void UpdateStateCrashKey(OomInterventionState next_state) {
+  static OomInterventionState current_state = OomInterventionState::None;
+  // Once an intervention is trigger, the state shall never go back to the
+  // Before state.
+  if (next_state == OomInterventionState::Before &&
+      current_state != OomInterventionState::None)
+    return;
+  if (current_state == next_state)
+    return;
+  current_state = next_state;
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "oom_intervention_state", base::debug::CrashKeySize::Size32);
+  switch (current_state) {
+    case OomInterventionState::None:
+      base::debug::SetCrashKeyString(crash_key, "none");
+      break;
+    case OomInterventionState::Before:
+      base::debug::SetCrashKeyString(crash_key, "before");
+      break;
+    case OomInterventionState::During:
+      base::debug::SetCrashKeyString(crash_key, "during");
+      break;
+    case OomInterventionState::After:
+      base::debug::SetCrashKeyString(crash_key, "after");
+      break;
+  }
+}
+}  // namespace
+
 // static
 void OomInterventionImpl::Create(mojom::blink::OomInterventionRequest request) {
   mojo::MakeStrongBinding(std::make_unique<OomInterventionImpl>(),
@@ -27,9 +68,12 @@
 OomInterventionImpl::OomInterventionImpl()
     : delayed_report_timer_(Thread::MainThread()->GetTaskRunner(),
                             this,
-                            &OomInterventionImpl::TimerFiredUMAReport) {}
+                            &OomInterventionImpl::TimerFiredUMAReport) {
+  UpdateStateCrashKey(OomInterventionState::Before);
+}
 
 OomInterventionImpl::~OomInterventionImpl() {
+  UpdateStateCrashKey(OomInterventionState::After);
   MemoryUsageMonitorInstance().RemoveObserver(this);
 }
 
@@ -83,6 +127,8 @@
   ReportMemoryStats(current_memory);
 
   if (oom_detected) {
+    UpdateStateCrashKey(OomInterventionState::During);
+
     if (navigate_ads_enabled_ || purge_v8_memory_enabled_) {
       for (const auto& page : Page::OrdinaryPages()) {
         for (Frame* frame = page->MainFrame(); frame;
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index f28b384b..920e8ec 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -2268,6 +2268,7 @@
     "//skia:skcms",
     "//testing/gmock",
     "//testing/gtest",
+    "//third_party/blink/renderer/core/accessibility:unit_tests",
     "//third_party/blink/renderer/core/editing:unit_tests",
     "//third_party/blink/renderer/core/fileapi:unit_tests",
   ]
diff --git a/third_party/blink/renderer/core/accessibility/BUILD.gn b/third_party/blink/renderer/core/accessibility/BUILD.gn
index dbff6a41..89a8e826 100644
--- a/third_party/blink/renderer/core/accessibility/BUILD.gn
+++ b/third_party/blink/renderer/core/accessibility/BUILD.gn
@@ -6,6 +6,8 @@
 
 blink_core_sources("accessibility") {
   sources = [
+    "apply_high_contrast_check.cc",
+    "apply_high_contrast_check.h",
     "ax_context.cc",
     "ax_context.h",
     "ax_object_cache.cc",
@@ -15,3 +17,9 @@
     "axid.h",
   ]
 }
+
+blink_core_tests("unit_tests") {
+  sources = [
+    "apply_high_contrast_check_test.cc",
+  ]
+}
diff --git a/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.cc b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.cc
new file mode 100644
index 0000000..a64108f1
--- /dev/null
+++ b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.cc
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/accessibility/apply_high_contrast_check.h"
+#include "third_party/blink/renderer/core/css/properties/css_property.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/platform/graphics/color.h"
+#include "third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.h"
+
+namespace blink {
+namespace {
+
+// TODO(https://crbug.com/925949): Add detection and classification of
+// background image color. Most sites with dark background images also have a
+// dark background color set, so this is less of a priority than it would be
+// otherwise.
+bool HasLightBackground(const LayoutObject& layout_object) {
+  const ComputedStyle& style = layout_object.StyleRef();
+  if (style.HasBackground()) {
+    Color color = style.VisitedDependentColor(GetCSSPropertyBackgroundColor());
+    return IsLight(color, style.Opacity());
+  }
+
+  // If we can't easily determine the background color, default to inverting the
+  // page.
+  return true;
+}
+
+}  // namespace
+
+bool ShouldApplyHighContrastFilterToPage(
+    HighContrastPagePolicy policy,
+    const LayoutObject& root_layout_object) {
+  switch (policy) {
+    case HighContrastPagePolicy::kFilterAll:
+      return true;
+    case HighContrastPagePolicy::kFilterByBackground:
+      return HasLightBackground(root_layout_object);
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.h b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.h
new file mode 100644
index 0000000..e664da80
--- /dev/null
+++ b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check.h
@@ -0,0 +1,31 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_HIGH_CONTRAST_CHECK_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_HIGH_CONTRAST_CHECK_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+
+namespace blink {
+
+// TODO(https://crbug.com/925949): Move this to high_contrast_settings.h.
+enum class HighContrastPagePolicy {
+  // Apply high-contrast filter to all frames, regardless of content.
+  kFilterAll,
+  // Apply high-contrast filter to frames based on background color.
+  kFilterByBackground,
+};
+
+// Determine whether the page with the provided |root_layout_object| should have
+// its colors inverted, based on the provided |policy|.
+//
+// This method does not check whether High Contrast Mode is enabled overall.
+bool CORE_EXPORT
+ShouldApplyHighContrastFilterToPage(HighContrastPagePolicy policy,
+                                    const LayoutObject& root_layout_object);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_APPLY_HIGH_CONTRAST_CHECK_H_
diff --git a/third_party/blink/renderer/core/accessibility/apply_high_contrast_check_test.cc b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check_test.cc
new file mode 100644
index 0000000..61587e57
--- /dev/null
+++ b/third_party/blink/renderer/core/accessibility/apply_high_contrast_check_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/accessibility/apply_high_contrast_check.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
+#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
+
+namespace blink {
+namespace {
+
+using ApplyHighContrastCheckTest = RenderingTest;
+
+TEST_F(ApplyHighContrastCheckTest, LightSolidBackgroundAlwaysFiltered) {
+  GetDocument().body()->SetInlineStyleProperty(CSSPropertyBackgroundColor,
+                                               CSSValueWhite);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterByBackground, GetLayoutView()));
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterAll, GetLayoutView()));
+}
+
+TEST_F(ApplyHighContrastCheckTest,
+       DarkSolidBackgroundFilteredIfPolicyIsFilterAll) {
+  GetDocument().body()->SetInlineStyleProperty(CSSPropertyBackgroundColor,
+                                               CSSValueBlack);
+  // TODO(https://crbug.com/925949): Set opacity the same way as the other CSS
+  // properties.
+  GetLayoutView().MutableStyle()->SetOpacity(0.9);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_FALSE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterByBackground, GetLayoutView()));
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterAll, GetLayoutView()));
+}
+
+TEST_F(ApplyHighContrastCheckTest, DarkLowOpacityBackgroundAlwaysFiltered) {
+  GetDocument().body()->SetInlineStyleProperty(CSSPropertyBackgroundColor,
+                                               CSSValueBlack);
+  // TODO(https://crbug.com/925949): Set opacity the same way as the other CSS
+  // properties.
+  GetLayoutView().MutableStyle()->SetOpacity(0.1);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterByBackground, GetLayoutView()));
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterAll, GetLayoutView()));
+}
+
+TEST_F(ApplyHighContrastCheckTest, DarkTransparentBackgroundAlwaysFiltered) {
+  GetDocument().body()->SetInlineStyleProperty(CSSPropertyBackgroundColor,
+                                               CSSValueTransparent);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterByBackground, GetLayoutView()));
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterAll, GetLayoutView()));
+}
+
+TEST_F(ApplyHighContrastCheckTest, BackgroundColorNotDefinedAlwaysFiltered) {
+  GetDocument().body()->RemoveInlineStyleProperty(CSSPropertyBackgroundColor);
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterByBackground, GetLayoutView()));
+  EXPECT_TRUE(ShouldApplyHighContrastFilterToPage(
+      HighContrastPagePolicy::kFilterAll, GetLayoutView()));
+}
+
+}  // namespace
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index f57e74b..f627dab 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3501,9 +3501,6 @@
       View()->UpdateLayout();
   }
 
-  if (View())
-    View()->InvokeFragmentAnchor();
-
   load_event_progress_ = kLoadEventCompleted;
 
   if (GetFrame() && GetLayoutView()) {
diff --git a/third_party/blink/renderer/core/dom/shadow_root.cc b/third_party/blink/renderer/core/dom/shadow_root.cc
index e9ddfb7..edec50e 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.cc
+++ b/third_party/blink/renderer/core/dom/shadow_root.cc
@@ -57,7 +57,6 @@
 
 struct SameSizeAsShadowRoot : public DocumentFragment, public TreeScope {
   Member<void*> member[3];
-  uint16_t counter;
   unsigned flags[1];
 };
 
diff --git a/third_party/blink/renderer/core/dom/shadow_root.h b/third_party/blink/renderer/core/dom/shadow_root.h
index 8ab5b0da..0fdb88b 100644
--- a/third_party/blink/renderer/core/dom/shadow_root.h
+++ b/third_party/blink/renderer/core/dom/shadow_root.h
@@ -190,13 +190,13 @@
   TraceWrapperMember<StyleSheetList> style_sheet_list_;
   Member<SlotAssignment> slot_assignment_;
   Member<ShadowRootV0> shadow_root_v0_;
-  uint16_t child_shadow_root_count_;
+  unsigned child_shadow_root_count_ : 16;
   unsigned type_ : 2;
   unsigned registered_with_parent_shadow_root_ : 1;
   unsigned delegates_focus_ : 1;
   unsigned slotting_ : 1;
   unsigned needs_distribution_recalc_ : 1;
-  unsigned unused_ : 26;
+  unsigned unused_ : 10;
 
   DISALLOW_COPY_AND_ASSIGN(ShadowRoot);
 };
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 559c3e7d..159b845 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1481,6 +1481,11 @@
                last_frame_time);
   DCHECK(!last_frame_time.is_null());
 
+  if (needs_hover_update_at_begin_frame_) {
+    MainFrameImpl()->GetFrame()->GetEventHandler().RecomputeMouseHoverState();
+    needs_hover_update_at_begin_frame_ = false;
+  }
+
   if (!MainFrameImpl())
     return;
 
@@ -3305,9 +3310,9 @@
                                    args.elastic_overscroll_delta.y());
   UpdateBrowserControlsConstraint(args.browser_controls_constraint);
 
-  if (RuntimeEnabledFeatures::NoHoverDuringScrollEnabled() &&
-      args.scroll_gesture_did_end)
-    MainFrameImpl()->GetFrame()->GetEventHandler().RecomputeMouseHoverState();
+  needs_hover_update_at_begin_frame_ =
+      args.scroll_gesture_did_end &&
+      RuntimeEnabledFeatures::NoHoverDuringScrollEnabled();
 }
 
 void WebViewImpl::RecordWheelAndTouchScrollingCount(
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 4412e83..5ebbe21 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -688,6 +688,8 @@
 
   FloatSize elastic_overscroll_;
 
+  bool needs_hover_update_at_begin_frame_ = false;
+
   Persistent<EventListener> popup_mouse_wheel_event_listener_;
 
   // The local root whose document has |popup_mouse_wheel_event_listener_|
diff --git a/third_party/blink/renderer/core/fileapi/public_url_manager.cc b/third_party/blink/renderer/core/fileapi/public_url_manager.cc
index e3de27c..9794137d 100644
--- a/third_party/blink/renderer/core/fileapi/public_url_manager.cc
+++ b/third_party/blink/renderer/core/fileapi/public_url_manager.cc
@@ -167,6 +167,9 @@
 void PublicURLManager::Resolve(
     const KURL& url,
     network::mojom::blink::URLLoaderFactoryRequest factory_request) {
+  if (is_stopped_)
+    return;
+
   DCHECK(BlobUtils::MojoBlobURLsEnabled());
   DCHECK(url.ProtocolIs("blob"));
   if (!url_store_) {
@@ -179,6 +182,9 @@
 void PublicURLManager::Resolve(
     const KURL& url,
     mojom::blink::BlobURLTokenRequest token_request) {
+  if (is_stopped_)
+    return;
+
   DCHECK(BlobUtils::MojoBlobURLsEnabled());
   DCHECK(url.ProtocolIs("blob"));
   if (!url_store_) {
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index cd58f1dda..f577759 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -540,7 +540,6 @@
     const String& nonce,
     const WTF::OrdinalNumber& context_line,
     const String& script_content,
-    InlineType inline_type,
     SecurityViolationReportingPolicy reporting_policy) const {
   DCHECK(element);
 
@@ -551,7 +550,8 @@
   bool is_allowed = true;
   for (const auto& policy : policies_) {
     is_allowed &=
-        CheckScriptHashAgainstPolicy(csp_hash_values, policy, inline_type) ||
+        CheckScriptHashAgainstPolicy(csp_hash_values, policy,
+                                     InlineType::kBlock) ||
         policy->AllowInlineScript(element, context_url, nonce, context_line,
                                   reporting_policy, script_content);
   }
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.h b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
index 93f9f71..e3f7485 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.h
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.h
@@ -355,7 +355,6 @@
                          const String& nonce,
                          const WTF::OrdinalNumber& context_line,
                          const String& script_content,
-                         InlineType,
                          SecurityViolationReportingPolicy =
                              SecurityViolationReportingPolicy::kReport) const;
   bool AllowInlineStyle(Element*,
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
index 2d9e5b9..3cb6c23 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy_test.cc
@@ -732,10 +732,9 @@
     policy->DidReceiveHeader(String("script-src ") + test.policy,
                              kContentSecurityPolicyHeaderTypeEnforce,
                              kContentSecurityPolicyHeaderSourceHTTP);
-    EXPECT_EQ(test.allowed,
-              policy->AllowInlineScript(
-                  element, context_url, String(test.nonce), context_line,
-                  content, ContentSecurityPolicy::InlineType::kBlock));
+    EXPECT_EQ(test.allowed, policy->AllowInlineScript(element, context_url,
+                                                      String(test.nonce),
+                                                      context_line, content));
     EXPECT_EQ(expected_reports, policy->violation_reports_sent_.size());
 
     // Enforce 'style-src'
@@ -757,8 +756,7 @@
                              kContentSecurityPolicyHeaderTypeReport,
                              kContentSecurityPolicyHeaderSourceHTTP);
     EXPECT_TRUE(policy->AllowInlineScript(
-        element, context_url, String(test.nonce), context_line, content,
-        ContentSecurityPolicy::InlineType::kBlock));
+        element, context_url, String(test.nonce), context_line, content));
     EXPECT_EQ(expected_reports, policy->violation_reports_sent_.size());
 
     // Report 'style-src'
@@ -1550,9 +1548,8 @@
   EXPECT_TRUE(csp->AllowScriptFromSource(
       example_url, nonce, IntegrityMetadataSet(), kParserInserted));
   EXPECT_TRUE(csp->AllowStyleFromSource(example_url, nonce));
-  EXPECT_TRUE(csp->AllowInlineScript(
-      element, context_url, nonce, ordinal_number, source,
-      ContentSecurityPolicy::InlineType::kBlock));
+  EXPECT_TRUE(csp->AllowInlineScript(element, context_url, nonce,
+                                     ordinal_number, source));
   EXPECT_TRUE(csp->AllowInlineStyle(element, context_url, nonce, ordinal_number,
                                     source,
                                     ContentSecurityPolicy::InlineType::kBlock));
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index 328d818..883429c6 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -467,17 +467,17 @@
                                               ->ToString());
   if (MixedContentChecker::IsMixedContent(source_document->GetSecurityOrigin(),
                                           target_url)) {
-    UseCounter::Count(source->GetFrame(),
+    UseCounter::Count(source_document,
                       WebFeature::kPostMessageFromSecureToInsecure);
   } else if (MixedContentChecker::IsMixedContent(
                  GetFrame()->GetSecurityContext()->GetSecurityOrigin(),
                  source_document->Url())) {
-    UseCounter::Count(source->GetFrame(),
+    UseCounter::Count(source_document,
                       WebFeature::kPostMessageFromInsecureToSecure);
     if (MixedContentChecker::IsMixedContent(
             GetFrame()->Tree().Top().GetSecurityContext()->GetSecurityOrigin(),
             source_document->Url())) {
-      UseCounter::Count(source->GetFrame(),
+      UseCounter::Count(source_document,
                         WebFeature::kPostMessageFromInsecureToSecureToplevel);
     }
   }
@@ -486,7 +486,7 @@
           target_url, RedirectStatus::kNoRedirect,
           SecurityViolationReportingPolicy::kSuppressReporting)) {
     UseCounter::Count(
-        source->GetFrame(),
+        source_document,
         WebFeature::kPostMessageOutgoingWouldBeBlockedByConnectSrc);
   }
   UserActivation* user_activation = nullptr;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 28c58bca..eb503ff 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1028,9 +1028,10 @@
 
   // Top navigation in sandbox with or w/o 'allow-top-navigation'.
   if (target_frame != this && sandboxed && target_frame == Tree().Top()) {
-    UseCounter::Count(this, WebFeature::kTopNavInSandbox);
+    UseCounter::Count(GetDocument(), WebFeature::kTopNavInSandbox);
     if (!has_user_gesture) {
-      UseCounter::Count(this, WebFeature::kTopNavInSandboxWithoutGesture);
+      UseCounter::Count(GetDocument(),
+                        WebFeature::kTopNavInSandboxWithoutGesture);
     }
   }
 
@@ -1051,11 +1052,11 @@
     if (IsAdSubframe())
       framebust_params |= kAdBit;
 
-    UseCounter::Count(this, WebFeature::kTopNavigationFromSubFrame);
+    UseCounter::Count(GetDocument(), WebFeature::kTopNavigationFromSubFrame);
     if (sandboxed) {  // Sandboxed with 'allow-top-navigation'.
-      UseCounter::Count(this, WebFeature::kTopNavInSandboxWithPerm);
+      UseCounter::Count(GetDocument(), WebFeature::kTopNavInSandboxWithPerm);
       if (!has_user_gesture) {
-        UseCounter::Count(this,
+        UseCounter::Count(GetDocument(),
                           WebFeature::kTopNavInSandboxWithPermButNoGesture);
       }
     }
@@ -1131,7 +1132,8 @@
       !HasTransientUserActivation(this, false /* check_if_main_thread */) &&
       !target_frame.GetSecurityContext()->GetSecurityOrigin()->CanAccess(
           SecurityOrigin::Create(destination_url).get())) {
-    UseCounter::Count(this, WebFeature::kOpenerNavigationWithoutGesture);
+    UseCounter::Count(GetDocument(),
+                      WebFeature::kOpenerNavigationWithoutGesture);
   }
 
   if (!is_allowed_navigation && !error_reason.IsNull())
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index f7dd1a5..3aa69c1 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -187,11 +187,11 @@
   IntSize scaled_size = image->Size();
   scaled_size.Scale(1 / cursor.ImageScaleFactor());
   if (scaled_size.Width() > 64 || scaled_size.Height() > 64) {
-    UseCounter::Count(frame, WebFeature::kCursorImageGT64x64);
+    UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageGT64x64);
   } else if (scaled_size.Width() > 32 || scaled_size.Height() > 32) {
-    UseCounter::Count(frame, WebFeature::kCursorImageGT32x32);
+    UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageGT32x32);
   } else {
-    UseCounter::Count(frame, WebFeature::kCursorImageLE32x32);
+    UseCounter::Count(frame->GetDocument(), WebFeature::kCursorImageLE32x32);
   }
 }
 
diff --git a/third_party/blink/renderer/core/frame/use_counter.cc b/third_party/blink/renderer/core/frame/use_counter.cc
index ec72ff3..35044a5 100644
--- a/third_party/blink/renderer/core/frame/use_counter.cc
+++ b/third_party/blink/renderer/core/frame/use_counter.cc
@@ -1320,7 +1320,7 @@
   if (!frame.GetSecurityContext()->GetSecurityOrigin()->CanAccess(topOrigin)) {
     // This frame is cross-origin with the top-level frame, and so would be
     // blocked without a feature policy.
-    UseCounter::Count(&frame, blocked_cross_origin);
+    UseCounter::Count(frame.GetDocument(), blocked_cross_origin);
     return;
   }
 
@@ -1330,7 +1330,7 @@
   const Frame* f = &frame;
   while (!f->IsMainFrame()) {
     if (!f->GetSecurityContext()->GetSecurityOrigin()->CanAccess(topOrigin)) {
-      UseCounter::Count(&frame, blocked_same_origin);
+      UseCounter::Count(frame.GetDocument(), blocked_same_origin);
       return;
     }
     f = f->Tree().Parent();
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index fdfba52d..699af61 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2216,7 +2216,8 @@
   DCHECK(!features.empty());
   // Assimilate all features used/performed by the browser into UseCounter.
   for (int feature : features) {
-    UseCounter::Count(GetFrame(), static_cast<WebFeature>(feature));
+    UseCounter::Count(GetFrame()->GetDocument(),
+                      static_cast<WebFeature>(feature));
   }
 }
 
@@ -2272,11 +2273,13 @@
 }
 
 void WebLocalFrameImpl::DidCallAddSearchProvider() {
-  UseCounter::Count(GetFrame(), WebFeature::kExternalAddSearchProvider);
+  UseCounter::Count(GetFrame()->GetDocument(),
+                    WebFeature::kExternalAddSearchProvider);
 }
 
 void WebLocalFrameImpl::DidCallIsSearchProviderInstalled() {
-  UseCounter::Count(GetFrame(), WebFeature::kExternalIsSearchProviderInstalled);
+  UseCounter::Count(GetFrame()->GetDocument(),
+                    WebFeature::kExternalIsSearchProviderInstalled);
 }
 
 void WebLocalFrameImpl::DispatchMessageEventWithOriginCheck(
diff --git a/third_party/blink/renderer/core/html/canvas/image_data.cc b/third_party/blink/renderer/core/html/canvas/image_data.cc
index d6258f2..b2024905 100644
--- a/third_party/blink/renderer/core/html/canvas/image_data.cc
+++ b/third_party/blink/renderer/core/html/canvas/image_data.cc
@@ -111,14 +111,14 @@
 
     if (!data->byteLength()) {
       return RaiseDOMExceptionAndReturnFalse(
-          exception_state, DOMExceptionCode::kIndexSizeError,
+          exception_state, DOMExceptionCode::kInvalidStateError,
           "The input data has zero elements.");
     }
 
     data_length = data->byteLength() / data->TypeSize();
     if (data_length % 4) {
       return RaiseDOMExceptionAndReturnFalse(
-          exception_state, DOMExceptionCode::kIndexSizeError,
+          exception_state, DOMExceptionCode::kInvalidStateError,
           "The input data length is not a multiple of 4.");
     }
 
diff --git a/third_party/blink/renderer/core/html/canvas/image_element_base.cc b/third_party/blink/renderer/core/html/canvas/image_element_base.cc
index 00a0774..ad3bb0f 100644
--- a/third_party/blink/renderer/core/html/canvas/image_element_base.cc
+++ b/third_party/blink/renderer/core/html/canvas/image_element_base.cc
@@ -159,9 +159,6 @@
               "dimensions, and no resize options or crop region are "
               "specified."));
     }
-  }
-
-  if (IsSVGSource()) {
     return ImageBitmap::CreateAsync(this, crop_rect,
                                     event_target.ToLocalDOMWindow()->document(),
                                     script_state, options);
diff --git a/third_party/blink/renderer/core/html/html_script_element.cc b/third_party/blink/renderer/core/html/html_script_element.cc
index a888f0b..8116751 100644
--- a/third_party/blink/renderer/core/html/html_script_element.cc
+++ b/third_party/blink/renderer/core/html/html_script_element.cc
@@ -243,11 +243,9 @@
 bool HTMLScriptElement::AllowInlineScriptForCSP(
     const AtomicString& nonce,
     const WTF::OrdinalNumber& context_line,
-    const String& script_content,
-    ContentSecurityPolicy::InlineType inline_type) {
+    const String& script_content) {
   return GetDocument().GetContentSecurityPolicy()->AllowInlineScript(
-      this, GetDocument().Url(), nonce, context_line, script_content,
-      inline_type);
+      this, GetDocument().Url(), nonce, context_line, script_content);
 }
 
 Document& HTMLScriptElement::GetDocument() const {
diff --git a/third_party/blink/renderer/core/html/html_script_element.h b/third_party/blink/renderer/core/html/html_script_element.h
index fa8b725..51d234d 100644
--- a/third_party/blink/renderer/core/html/html_script_element.h
+++ b/third_party/blink/renderer/core/html/html_script_element.h
@@ -101,8 +101,7 @@
   }
   bool AllowInlineScriptForCSP(const AtomicString& nonce,
                                const WTF::OrdinalNumber&,
-                               const String& script_content,
-                               ContentSecurityPolicy::InlineType) override;
+                               const String& script_content) override;
   void DispatchLoadEvent() override;
   void DispatchErrorEvent() override;
   void SetScriptElementForBinding(
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
index c601ed51..157d644 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
@@ -300,12 +300,8 @@
   EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(0);
   platform_->RunForPeriodSeconds(1);
 
-  EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
-  Video()->pause();
-  platform_->RunUntilIdle();
-
-  // Seek to some time in the past. A completed seek while paused should trigger
-  // a *single* timeupdate.
+  // Seek to some time in the past. A completed seek should trigger a *single*
+  // timeupdate.
   EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
   ASSERT_GE(WebMediaPlayer()->CurrentTime(), 1);
   Video()->setCurrentTime(WebMediaPlayer()->CurrentTime() - 1);
@@ -358,14 +354,10 @@
   ASSERT_GE(WebMediaPlayer()->CurrentTime(), 1);
   Video()->setCurrentTime(WebMediaPlayer()->CurrentTime() - 1);
   WebMediaPlayer()->FinishSeek();
-
-  // Expect another timeupdate after FinishSeek due to
-  // seeking -> begin scrubbing -> pause -> timeupdate.
-  EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
   platform_->RunUntilIdle();
 
   // Advancing the remainder of the last periodic timeupdate interval should be
-  // insufficient to trigger a new timeupdate event because the seek's
+  // insufficient to triggger a new timeupdate event because the seek's
   // timeupdate occurred only 125ms ago. We desire to fire periodic timeupdates
   // exactly every 250ms from the last timeupdate, and the seek's timeupdate
   // should reset that 250ms ms countdown.
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
index 70950b9..ec3a2d57 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
@@ -8,6 +8,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/numerics/checked_math.h"
 #include "base/single_thread_task_runner.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
 #include "third_party/blink/renderer/core/html/canvas/image_data.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
@@ -971,8 +972,8 @@
       ParseOptions(options, crop_rect, image->BitmapSourceSize());
   if (DstBufferSizeHasOverflow(parsed_options)) {
     resolver->Reject(
-        ScriptValue(resolver->GetScriptState(),
-                    v8::Null(resolver->GetScriptState()->GetIsolate())));
+        DOMException::Create(DOMExceptionCode::kInvalidStateError,
+                             "The ImageBitmap could not be allocated."));
     return promise;
   }
 
@@ -990,8 +991,8 @@
       resolver->Resolve(bitmap);
     } else {
       resolver->Reject(
-          ScriptValue(resolver->GetScriptState(),
-                      v8::Null(resolver->GetScriptState()->GetIsolate())));
+          DOMException::Create(DOMExceptionCode::kInvalidStateError,
+                               "The ImageBitmap could not be allocated."));
     }
     return promise;
   }
diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc
index 607cad7..94f14ed 100644
--- a/third_party/blink/renderer/core/input/event_handler_test.cc
+++ b/third_party/blink/renderer/core/input/event_handler_test.cc
@@ -1254,4 +1254,88 @@
   EXPECT_FALSE(iframe_doc->GetActiveElement());
 }
 
+// Test that the hover is updated at the next begin frame after the scroll ends.
+TEST_F(EventHandlerSimTest, TestUpdateHoverAfterScrollAtBeginFrame) {
+  RuntimeEnabledFeatures::SetNoHoverDuringScrollEnabled(true);
+  WebView().MainFrameWidget()->Resize(WebSize(800, 600));
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body, html {
+        margin: 0;
+      }
+      div {
+        height: 300px;
+        width: 100%;
+      }
+    </style>
+    <body>
+    <div class="hoverme" id="line1">hover over me</div>
+    <div class="hoverme" id="line2">hover over me</div>
+    <div class="hoverme" id="line3">hover over me</div>
+    <div class="hoverme" id="line4">hover over me</div>
+    <div class="hoverme" id="line5">hover over me</div>
+    </body>
+    <script>
+      let array = document.getElementsByClassName('hoverme');
+      for (let element of array) {
+        element.addEventListener('mouseover', function (e) {
+          this.innerHTML = "currently hovered";
+        });
+        element.addEventListener('mouseout', function (e) {
+          this.innerHTML = "was hovered";
+        });
+      }
+    </script>
+  )HTML");
+  Compositor().BeginFrame();
+
+  // Set mouse position and active web view.
+  WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown, WebFloatPoint(1, 1),
+                                 WebFloatPoint(1, 1),
+                                 WebPointerProperties::Button::kLeft, 1,
+                                 WebInputEvent::Modifiers::kLeftButtonDown,
+                                 WebInputEvent::GetStaticTimeStampForTests());
+  mouse_down_event.SetFrameScale(1);
+  GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
+      mouse_down_event);
+
+  WebMouseEvent mouse_up_event(
+      WebInputEvent::kMouseUp, WebFloatPoint(1, 1), WebFloatPoint(1, 1),
+      WebPointerProperties::Button::kLeft, 1, WebInputEvent::kNoModifiers,
+      WebInputEvent::GetStaticTimeStampForTests());
+  mouse_up_event.SetFrameScale(1);
+  GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
+      mouse_up_event);
+
+  WebView().MainFrameWidget()->SetFocus(true);
+  WebView().SetIsActive(true);
+
+  WebElement element1 = GetDocument().getElementById("line1");
+  WebElement element2 = GetDocument().getElementById("line2");
+  WebElement element3 = GetDocument().getElementById("line3");
+  EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
+  EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
+  EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
+
+  // Do a compositor scroll and set |scroll_gesture_did_end| to be true.
+  LocalFrameView* frame_view = GetDocument().View();
+  frame_view->LayoutViewport()->DidScroll(FloatPoint(0, 500));
+  WebView().MainFrameWidget()->ApplyViewportChanges(
+      {gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f, 0,
+       cc::BrowserControlsState::kBoth, true});
+  ASSERT_EQ(500, frame_view->LayoutViewport()->GetScrollOffset().Height());
+  EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
+  EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
+  EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
+
+  // The fake mouse move event is dispatched at the begin frame to update hover.
+  Compositor().BeginFrame();
+  EXPECT_EQ("was hovered", element1.InnerHTML().Utf8());
+  EXPECT_EQ("currently hovered", element2.InnerHTML().Utf8());
+  EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_memory_agent.cc b/third_party/blink/renderer/core/inspector/inspector_memory_agent.cc
index 45056c7e..45fb50f 100644
--- a/third_party/blink/renderer/core/inspector/inspector_memory_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_memory_agent.cc
@@ -172,7 +172,7 @@
       protocol::Array<protocol::Memory::Module>::create();
   for (const auto* module : module_cache.GetModules()) {
     modules->addItem(protocol::Memory::Module::create()
-                         .setName(module->GetFilename().value().c_str())
+                         .setName(module->GetDebugBasename().value().c_str())
                          .setUuid(module->GetId().c_str())
                          .setBaseAddress(String::Format(
                              "0x%" PRIxPTR, module->GetBaseAddress()))
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index 3be4c70..4310471 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -57,6 +57,7 @@
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
 #include "third_party/blink/renderer/core/layout/logical_values.h"
+#include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
 #include "third_party/blink/renderer/core/layout/text_autosizer.h"
 #include "third_party/blink/renderer/core/page/page.h"
@@ -566,7 +567,8 @@
 void LayoutBlock::AddVisualOverflowFromBlockChildren() {
   for (LayoutBox* child = FirstChildBox(); child;
        child = child->NextSiblingBox()) {
-    if (child->IsFloatingOrOutOfFlowPositioned() || child->IsColumnSpanAll())
+    if ((!IsLayoutNGContainingBlock(this) && child->IsFloating()) ||
+        child->IsOutOfFlowPositioned() || child->IsColumnSpanAll())
       continue;
 
     // If the child contains inline with outline and continuation, its
@@ -584,7 +586,8 @@
 void LayoutBlock::AddLayoutOverflowFromBlockChildren() {
   for (LayoutBox* child = FirstChildBox(); child;
        child = child->NextSiblingBox()) {
-    if (child->IsFloatingOrOutOfFlowPositioned() || child->IsColumnSpanAll())
+    if ((!IsLayoutNGContainingBlock(this) && child->IsFloating()) ||
+        child->IsOutOfFlowPositioned() || child->IsColumnSpanAll())
       continue;
 
     // If the child contains inline with outline and continuation, its
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.cc b/third_party/blink/renderer/core/layout/layout_block_flow.cc
index 990c4e7..b34d3c4 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.cc
@@ -2566,8 +2566,9 @@
   AddVisualEffectOverflow();
   AddVisualOverflowFromTheme();
 
-  if (recompute_floats || CreatesNewFormattingContext() ||
-      HasSelfPaintingLayer())
+  if (!IsLayoutNGContainingBlock(this) &&
+      (recompute_floats || CreatesNewFormattingContext() ||
+       HasSelfPaintingLayer()))
     AddVisualOverflowFromFloats();
   if (VisualOverflowRect() != previous_visual_overflow_rect) {
     SetShouldCheckForPaintInvalidation();
@@ -2580,8 +2581,9 @@
   LayoutBlock::ComputeLayoutOverflow(old_client_after_edge, recompute_floats);
   // TODO(chrishtr): why does it check for a self-painting layer? That should
   // only apply to visual overflow.
-  if (recompute_floats || CreatesNewFormattingContext() ||
-      HasSelfPaintingLayer())
+  if (!IsLayoutNGContainingBlock(this) &&
+      (recompute_floats || CreatesNewFormattingContext() ||
+       HasSelfPaintingLayer()))
     AddLayoutOverflowFromFloats();
 }
 
diff --git a/third_party/blink/renderer/core/layout/line/inline_text_box.cc b/third_party/blink/renderer/core/layout/line/inline_text_box.cc
index 678d192..f7476abf 100644
--- a/third_party/blink/renderer/core/layout/line/inline_text_box.cc
+++ b/third_party/blink/renderer/core/layout/line/inline_text_box.cc
@@ -315,7 +315,7 @@
   GetLineLayoutItem().AttachTextBox(this);
 }
 
-void InlineTextBox::SetTruncation(unsigned truncation) {
+void InlineTextBox::SetTruncation(uint16_t truncation) {
   if (truncation == truncation_)
     return;
 
diff --git a/third_party/blink/renderer/core/layout/line/inline_text_box.h b/third_party/blink/renderer/core/layout/line/inline_text_box.h
index 15ed072..bbf76b8 100644
--- a/third_party/blink/renderer/core/layout/line/inline_text_box.h
+++ b/third_party/blink/renderer/core/layout/line/inline_text_box.h
@@ -176,7 +176,7 @@
 
  private:
   bool IsBoxEndIncludedInSelection() const;
-  void SetTruncation(unsigned);
+  void SetTruncation(uint16_t);
 
   void ClearTruncation() final;
   LayoutUnit PlaceEllipsisBox(bool flow_is_ltr,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc
index 972dd3c..24edfc6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm_test.cc
@@ -1243,26 +1243,6 @@
   EXPECT_THAT(left_float->OffsetLeft(), 88);
   // 30 = body_top_offset(collapsed margins result) + float's padding
   EXPECT_THAT(left_float->OffsetTop(), body_top_offset + 10);
-
-  // ** Legacy Floating objects **
-  Element* empty2 = GetDocument().getElementById("empty2");
-  auto& floating_objects =
-      const_cast<FloatingObjects*>(
-          ToLayoutBlockFlow(empty2->GetLayoutObject())->GetFloatingObjects())
-          ->MutableSet();
-  ASSERT_EQ(2UL, floating_objects.size());
-  auto left_floating_object = floating_objects.TakeFirst();
-  // 80 = float_inline_offset(25) + accumulative offset of empty blocks(35 + 20)
-  EXPECT_THAT(left_floating_object->X(), LayoutUnit(15));
-  // 10 = left float's margin
-  EXPECT_THAT(left_floating_object->Y(), LayoutUnit());
-
-  auto right_floating_object = floating_objects.TakeFirst();
-  // 150 = float_inline_offset(25) +
-  //       right float offset(125)
-  EXPECT_THAT(right_floating_object->X(), LayoutUnit(140));
-  // 15 = right float's margin
-  EXPECT_THAT(right_floating_object->Y(), LayoutUnit(0));
 }
 
 // Verifies that left/right floating and regular blocks can be positioned
@@ -1684,17 +1664,6 @@
       <div id="empty-block2"></div>
     </div>
   )HTML");
-  // #container is the new parent for our float because it's height != 0.
-  Element* container = GetDocument().getElementById("container");
-  auto& floating_objects =
-      const_cast<FloatingObjects*>(
-          ToLayoutBlockFlow(container->GetLayoutObject())->GetFloatingObjects())
-          ->MutableSet();
-  ASSERT_EQ(1UL, floating_objects.size());
-  auto floating_object = floating_objects.TakeFirst();
-  // left-float's margin = 15.
-  EXPECT_THAT(floating_object->X(), LayoutUnit());
-  EXPECT_THAT(floating_object->Y(), LayoutUnit());
 
   scoped_refptr<const NGPhysicalBoxFragment> html_fragment;
   std::tie(html_fragment, std::ignore) = RunBlockLayoutAlgorithmForElement(
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 c8bdaec..06034ab 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
@@ -122,43 +122,6 @@
   return minmax;
 }
 
-bool IsFloatFragment(const NGPhysicalFragment& fragment) {
-  const LayoutObject* layout_object = fragment.GetLayoutObject();
-  return layout_object && layout_object->IsFloating() && fragment.IsBox();
-}
-
-// Creates a blink::FloatingObject (if needed), and populates it with the
-// position information needed by the existing layout tree.
-void CopyFloatChildFragmentPosition(LayoutBox* floating_box,
-                                    const NGPhysicalOffset offset,
-                                    bool has_flipped_x_axis) {
-  DCHECK(floating_box->IsFloating());
-
-  LayoutBlock* containing_block = floating_box->ContainingBlock();
-  DCHECK(containing_block);
-
-  // Floats need an associated FloatingObject for painting.
-  FloatingObject* floating_object =
-      ToLayoutBlockFlow(containing_block)->InsertFloatingObject(*floating_box);
-  floating_object->SetShouldPaint(!floating_box->HasSelfPaintingLayer());
-  LayoutUnit horizontal_margin_edge_offset = offset.left;
-  if (has_flipped_x_axis)
-    horizontal_margin_edge_offset -= floating_box->MarginRight();
-  else
-    horizontal_margin_edge_offset -= floating_box->MarginLeft();
-  floating_object->SetX(horizontal_margin_edge_offset);
-  floating_object->SetY(offset.top - floating_box->MarginTop());
-#if DCHECK_IS_ON()
-  // Being "placed" is a legacy thing. Make sure the flags remain unset in NG.
-  DCHECK(!floating_object->IsPlaced());
-  DCHECK(!floating_object->IsInPlacedTree());
-
-  // Set this flag to tell the float machinery that it's safe to read out
-  // position data.
-  floating_object->SetHasGeometry();
-#endif
-}
-
 void UpdateLegacyMultiColumnFlowThread(
     NGBlockNode node,
     LayoutMultiColumnFlowThread* flow_thread,
@@ -687,8 +650,6 @@
     const NGPhysicalOffset& offset_from_start) {
   LayoutBox* rendered_legend = nullptr;
   for (const auto& child_fragment : physical_fragment.Children()) {
-    auto* child_object = child_fragment->GetLayoutObject();
-
     // Skip any line-boxes we have as children, this is handled within
     // NGInlineNode at the moment.
     if (!child_fragment->IsBox() && !child_fragment->IsRenderedLegend())
@@ -701,10 +662,6 @@
       CopyChildFragmentPosition(box_fragment, child_fragment.Offset(),
                                 offset_from_start);
     }
-    if (child_object->IsLayoutBlockFlow()) {
-      ToLayoutBlockFlow(child_object)->AddVisualOverflowFromFloats();
-      ToLayoutBlockFlow(child_object)->AddLayoutOverflowFromFloats();
-    }
   }
 
   if (rendered_legend) {
@@ -778,11 +735,6 @@
   }
   layout_box->SetLocation(LayoutPoint(
       horizontal_offset, fragment_offset.top + additional_offset.top));
-
-  if (IsFloatFragment(fragment)) {
-    CopyFloatChildFragmentPosition(
-        layout_box, fragment_offset + additional_offset, has_flipped_x_axis);
-  }
 }
 
 // For inline children, NG painters handles fragments directly, but there are
@@ -809,11 +761,6 @@
                                       maybe_flipped_offset.left;
         }
         layout_box.SetLocation(maybe_flipped_offset.ToLayoutPoint());
-
-        if (IsFloatFragment(*child)) {
-          CopyFloatChildFragmentPosition(&layout_box, maybe_flipped_offset,
-                                         initial_container_is_flipped);
-        }
       }
 
       // Legacy compatibility. This flag is used in paint layer for
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 70e6c1ab..4fa0ab6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -31,8 +31,8 @@
   STACK_ALLOCATED();
 
  public:
-  typedef Vector<scoped_refptr<const NGPhysicalFragment>, 16> ChildrenVector;
-  typedef Vector<NGLogicalOffset, 16> OffsetVector;
+  typedef Vector<scoped_refptr<const NGPhysicalFragment>, 4> ChildrenVector;
+  typedef Vector<NGLogicalOffset, 4> OffsetVector;
 
   LayoutUnit BfcLineOffset() const { return bfc_line_offset_; }
   NGContainerFragmentBuilder& SetBfcLineOffset(LayoutUnit bfc_line_offset) {
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 28b2840f..cb39420 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -533,7 +533,8 @@
   if (!error) {
     fetcher_->Context().DispatchDidFinishLoading(
         main_resource_identifier_, completion_time, total_encoded_data_length,
-        total_decoded_body_length, should_report_corb_blocking);
+        total_decoded_body_length, should_report_corb_blocking,
+        FetchContext::ResourceResponseType::kNotFromMemoryCache);
     if (response_.IsHTTP()) {
       navigation_timing_info_->SetFinalResponse(response_);
       navigation_timing_info_->AddFinalTransferSize(
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index 413bd45..d0b428bf 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -91,6 +91,7 @@
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/performance.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
+#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
 #include "third_party/blink/renderer/platform/histogram.h"
@@ -580,7 +581,8 @@
     TimeTicks finish_time,
     int64_t encoded_data_length,
     int64_t decoded_body_length,
-    bool should_report_corb_blocking) {
+    bool should_report_corb_blocking,
+    ResourceResponseType response_type) {
   if (GetResourceFetcherProperties().IsDetached())
     return;
 
@@ -588,13 +590,25 @@
   probe::didFinishLoading(Probe(), identifier, MasterDocumentLoader(),
                           finish_time, encoded_data_length, decoded_body_length,
                           should_report_corb_blocking);
-  if (frame_or_imported_document_->GetDocument()) {
-    InteractiveDetector* interactive_detector(
-        InteractiveDetector::From(*frame_or_imported_document_->GetDocument()));
-    if (interactive_detector) {
-      interactive_detector->OnResourceLoadEnd(finish_time);
+
+  Document* document = frame_or_imported_document_->GetDocument();
+  if (!document) {
+    return;
+  }
+
+  if (auto* interactive_detector = InteractiveDetector::From(*document)) {
+    interactive_detector->OnResourceLoadEnd(finish_time);
+  }
+
+  if (LocalFrame* frame = document->GetFrame()) {
+    if (IdlenessDetector* idleness_detector = frame->GetIdlenessDetector()) {
+      idleness_detector->OnDidLoadResource();
     }
   }
+
+  if (response_type == ResourceResponseType::kNotFromMemoryCache) {
+    document->CheckCompleted();
+  }
 }
 
 void FrameFetchContext::DispatchDidFail(const KURL& url,
@@ -616,21 +630,28 @@
 
   GetFrame()->Loader().Progress().CompleteProgress(identifier);
   probe::didFailLoading(Probe(), identifier, MasterDocumentLoader(), error);
-  if (frame_or_imported_document_->GetDocument()) {
-    InteractiveDetector* interactive_detector(
-        InteractiveDetector::From(*frame_or_imported_document_->GetDocument()));
-    if (interactive_detector) {
-      // We have not yet recorded load_finish_time. Pass nullopt here; we will
-      // call CurrentTimeTicksInSeconds lazily when we need it.
-      interactive_detector->OnResourceLoadEnd(base::nullopt);
-    }
-  }
   // Notification to FrameConsole should come AFTER InspectorInstrumentation
   // call, DevTools front-end relies on this.
   if (!is_internal_request) {
     GetFrame()->Console().DidFailLoading(MasterDocumentLoader(), identifier,
                                          error);
   }
+  Document* document = frame_or_imported_document_->GetDocument();
+  if (!document) {
+    return;
+  }
+
+  if (auto* interactive_detector = InteractiveDetector::From(*document)) {
+    // We have not yet recorded load_finish_time. Pass nullopt here; we will
+    // call CurrentTimeTicksInSeconds lazily when we need it.
+    interactive_detector->OnResourceLoadEnd(base::nullopt);
+  }
+  if (LocalFrame* frame = document->GetFrame()) {
+    if (IdlenessDetector* idleness_detector = frame->GetIdlenessDetector()) {
+      idleness_detector->OnDidLoadResource();
+    }
+  }
+  document->CheckCompleted();
 }
 
 void FrameFetchContext::RecordLoadingActivity(
@@ -656,21 +677,6 @@
   }
 }
 
-void FrameFetchContext::DidLoadResource(Resource* resource) {
-  if (GetResourceFetcherProperties().IsDetached() ||
-      !frame_or_imported_document_->GetDocument())
-    return;
-  if (LocalFrame* local_frame =
-          frame_or_imported_document_->GetDocument()->GetFrame()) {
-    if (IdlenessDetector* idleness_detector =
-            local_frame->GetIdlenessDetector()) {
-      idleness_detector->OnDidLoadResource();
-    }
-  }
-
-  frame_or_imported_document_->GetDocument()->CheckCompleted();
-}
-
 void FrameFetchContext::DidObserveLoadingBehavior(
     WebLoadingBehaviorFlag behavior) {
   if (GetDocumentLoader())
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index 0ee86601..30963d1 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -115,7 +115,8 @@
                                 TimeTicks finish_time,
                                 int64_t encoded_data_length,
                                 int64_t decoded_body_length,
-                                bool should_report_corb_blocking) override;
+                                bool should_report_corb_blocking,
+                                ResourceResponseType) override;
   void DispatchDidFail(const KURL&,
                        unsigned long identifier,
                        const ResourceError&,
@@ -125,7 +126,6 @@
   void RecordLoadingActivity(const ResourceRequest&,
                              ResourceType,
                              const AtomicString& fetch_initiator_name) override;
-  void DidLoadResource(Resource*) override;
   void DidObserveLoadingBehavior(WebLoadingBehaviorFlag) override;
 
   void AddResourceTiming(const ResourceTimingInfo&) override;
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index 66b0dcdfb..063521d 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -1305,7 +1305,8 @@
   dummy_page_holder = nullptr;
 
   GetFetchContext()->DispatchDidFinishLoading(
-      4, base::TimeTicks() + base::TimeDelta::FromSecondsD(0.3), 8, 10, false);
+      4, base::TimeTicks() + base::TimeDelta::FromSecondsD(0.3), 8, 10, false,
+      FetchContext::ResourceResponseType::kNotFromMemoryCache);
   // Should not crash.
 }
 
@@ -1339,17 +1340,6 @@
   // Should not crash.
 }
 
-TEST_F(FrameFetchContextTest, DidLoadResourceWhenDetached) {
-  ResourceRequest request(KURL("https://www.example.com/"));
-  request.SetFetchCredentialsMode(network::mojom::FetchCredentialsMode::kOmit);
-  Resource* resource = MockResource::Create(request);
-
-  dummy_page_holder = nullptr;
-
-  GetFetchContext()->DidLoadResource(resource);
-  // Should not crash.
-}
-
 TEST_F(FrameFetchContextTest, AddResourceTimingWhenDetached) {
   scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
       "type", base::TimeTicks() + base::TimeDelta::FromSecondsD(0.3));
diff --git a/third_party/blink/renderer/core/loader/http_equiv.cc b/third_party/blink/renderer/core/loader/http_equiv.cc
index 798c6c7f..85ce2f43 100644
--- a/third_party/blink/renderer/core/loader/http_equiv.cc
+++ b/third_party/blink/renderer/core/loader/http_equiv.cc
@@ -184,7 +184,6 @@
   UseCounter::Count(document, WebFeature::kMetaRefresh);
   if (!document.GetContentSecurityPolicy()->AllowInlineScript(
           element, NullURL(), "", OrdinalNumber(), "",
-          ContentSecurityPolicy::InlineType::kBlock,
           SecurityViolationReportingPolicy::kSuppressReporting)) {
     UseCounter::Count(document,
                       WebFeature::kMetaRefreshWhenCSPBlocksInlineScript);
@@ -200,7 +199,6 @@
 
   if (!document.GetContentSecurityPolicy()->AllowInlineScript(
           element, NullURL(), "", OrdinalNumber(), "",
-          ContentSecurityPolicy::InlineType::kBlock,
           SecurityViolationReportingPolicy::kSuppressReporting)) {
     UseCounter::Count(document,
                       WebFeature::kMetaSetCookieWhenCSPBlocksInlineScript);
diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.cc b/third_party/blink/renderer/core/loader/worker_fetch_context.cc
index aabef2993..f0171f6 100644
--- a/third_party/blink/renderer/core/loader/worker_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/worker_fetch_context.cc
@@ -266,7 +266,8 @@
     TimeTicks finish_time,
     int64_t encoded_data_length,
     int64_t decoded_body_length,
-    bool should_report_corb_blocking) {
+    bool should_report_corb_blocking,
+    ResourceResponseType) {
   probe::didFinishLoading(Probe(), identifier, nullptr, finish_time,
                           encoded_data_length, decoded_body_length,
                           should_report_corb_blocking);
diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.h b/third_party/blink/renderer/core/loader/worker_fetch_context.h
index 245f423..2939779 100644
--- a/third_party/blink/renderer/core/loader/worker_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/worker_fetch_context.h
@@ -97,7 +97,8 @@
                                 TimeTicks finish_time,
                                 int64_t encoded_data_length,
                                 int64_t decoded_body_length,
-                                bool should_report_corb_blocking) override;
+                                bool should_report_corb_blocking,
+                                ResourceResponseType) override;
   void DispatchDidFail(const KURL&,
                        unsigned long identifier,
                        const ResourceError&,
diff --git a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc
index c22426d..965590d 100644
--- a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/html/html_anchor_element.h"
+#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
@@ -21,7 +22,16 @@
 
 using test::RunPendingTasks;
 
-class ElementFragmentAnchorTest : public SimTest {};
+class ElementFragmentAnchorTest : public SimTest {
+  void SetUp() override {
+    SimTest::SetUp();
+
+    // Focus handlers aren't run unless the page is focused.
+    GetDocument().GetPage()->GetFocusController().SetFocused(true);
+
+    WebView().MainFrameWidget()->Resize(WebSize(800, 600));
+  }
+};
 
 // Ensure that the focus event handler is run before the rAF callback. We'll
 // change the background color from a rAF set in the focus handler and make
@@ -54,9 +64,6 @@
       });
   )HTML"));
 
-  // Focus handlers aren't run unless the page is focused.
-  GetDocument().GetPage()->GetFocusController().SetFocused(true);
-
   // We're still waiting on the stylesheet to load so the load event shouldn't
   // yet dispatch and rendering is deferred.
   ASSERT_FALSE(GetDocument().IsRenderingReady());
@@ -89,6 +96,118 @@
             Color(0, 255, 0).NameForLayoutTreeAsText());
 }
 
+// This test ensures that when an iframe's document is closed, and the parent
+// has dirty layout, the iframe is laid out prior to invoking its fragment
+// anchor. Without performing this layout, the anchor cannot scroll to the
+// correct location and it will be cleared since the document is closed.
+TEST_F(ElementFragmentAnchorTest, IframeFragmentNoLayoutUntilLoad) {
+  SimRequest main_resource("https://example.com/test.html", "text/html");
+  SimRequest child_resource("https://example.com/child.html#fragment",
+                            "text/html");
+  LoadURL("https://example.com/test.html");
+
+  // Don't clcose the main document yet, since that'll cause it to layout.
+  main_resource.Write(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        iframe {
+          border: 0;
+          width: 300px;
+          height: 200px;
+        }
+      </style>
+      <iframe id="child" src="child.html#fragment"></iframe>
+    )HTML");
+
+  // When the iframe document is loaded, it'll try to scroll the fragment into
+  // view. Ensure it does so correctly by laying out first.
+  child_resource.Complete(R"HTML(
+      <!DOCTYPE html>
+      <div style="height:500px;">content</div>
+      <div id="fragment">fragment content</div>
+    )HTML");
+  Compositor().BeginFrame();
+
+  HTMLFrameOwnerElement* iframe =
+      To<HTMLFrameOwnerElement>(GetDocument().getElementById("child"));
+  ScrollableArea* child_viewport =
+      iframe->contentDocument()->View()->LayoutViewport();
+  Element* fragment = iframe->contentDocument()->getElementById("fragment");
+
+  IntRect fragment_rect_in_frame(
+      fragment->GetLayoutObject()->AbsoluteBoundingBoxRect());
+  IntRect viewport_rect(IntPoint(),
+                        child_viewport->VisibleContentRect().Size());
+
+  EXPECT_TRUE(viewport_rect.Contains(fragment_rect_in_frame))
+      << "Fragment element at [" << fragment_rect_in_frame.ToString()
+      << "] was not scrolled into viewport rect [" << viewport_rect.ToString()
+      << "]";
+
+  main_resource.Finish();
+}
+
+// This test ensures that we correctly scroll the fragment into view in the
+// case that the iframe has finished load but layout becomes dirty (in both
+// parent and iframe) before we've had a chance to scroll the fragment into
+// view.
+TEST_F(ElementFragmentAnchorTest, IframeFragmentDirtyLayoutAfterLoad) {
+  SimRequest main_resource("https://example.com/test.html", "text/html");
+  SimRequest child_resource("https://example.com/child.html#fragment",
+                            "text/html");
+  LoadURL("https://example.com/test.html");
+
+  // Don't clcose the main document yet, since that'll cause it to layout.
+  main_resource.Write(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        iframe {
+          border: 0;
+          width: 300px;
+          height: 200px;
+        }
+      </style>
+      <iframe id="child" src="child.html#fragment"></iframe>
+    )HTML");
+
+  // Use text so that changing the iframe width will change the y-location of
+  // the fragment.
+  child_resource.Complete(R"HTML(
+      <!DOCTYPE html>
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum
+      <div id="fragment">fragment content</div>
+    )HTML");
+
+  HTMLFrameOwnerElement* iframe =
+      To<HTMLFrameOwnerElement>(GetDocument().getElementById("child"));
+  iframe->setAttribute(html_names::kStyleAttr, "width:100px");
+
+  Compositor().BeginFrame();
+
+  ScrollableArea* child_viewport =
+      iframe->contentDocument()->View()->LayoutViewport();
+  Element* fragment = iframe->contentDocument()->getElementById("fragment");
+
+  IntRect fragment_rect_in_frame(
+      fragment->GetLayoutObject()->AbsoluteBoundingBoxRect());
+  IntRect viewport_rect(IntPoint(),
+                        child_viewport->VisibleContentRect().Size());
+
+  EXPECT_TRUE(viewport_rect.Contains(fragment_rect_in_frame))
+      << "Fragment element at [" << fragment_rect_in_frame.ToString()
+      << "] was not scrolled into viewport rect [" << viewport_rect.ToString()
+      << "]";
+
+  main_resource.Finish();
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
index 64d9c50a..311c29e 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
@@ -249,12 +249,13 @@
     return;
   }
 
-  layer.CcLayer()->SetOffsetToTransformParent(
-      gfx::Vector2dF(FloatPoint(layer.GetOffsetFromTransformNode())));
+  auto offset = layer.GetOffsetFromTransformNode();
+  gfx::Vector2dF layer_offset = gfx::Vector2dF(offset.X(), offset.Y());
   PaintChunkSubset paint_chunks =
       PaintChunkSubset(layer.GetPaintController().PaintChunks());
-  PaintArtifactCompositor::UpdateTouchActionRects(
-      layer.CcLayer(), layer.GetPropertyTreeState(), paint_chunks);
+  PaintArtifactCompositor::UpdateTouchActionRects(layer.CcLayer(), layer_offset,
+                                                  layer.GetPropertyTreeState(),
+                                                  paint_chunks);
 }
 
 static void ClearPositionConstraintExceptForLayer(GraphicsLayer* layer,
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index f76c0f3..a03828a 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -443,28 +443,34 @@
                                    full_context_.direct_compositing_reasons))
     return;
 
-  // We should use the same subpixel paint offset values for snapping
-  // regardless of whether a transform is present. If there is a transform
-  // we round the paint offset but keep around the residual fractional
-  // component for the transformed content to paint with.  In spv1 this was
-  // called "subpixel accumulation". For more information, see
-  // PaintLayer::subpixelAccumulation() and
-  // PaintLayerPainter::paintFragmentByApplyingTransform.
+  // We should use the same subpixel paint offset values for snapping regardless
+  // of paint offset translation. If we create a paint offset translation we
+  // round the paint offset but keep around the residual fractional component
+  // (i.e. subpixel accumulation) for the transformed content to paint with.
+  // In pre-CompositeAfterPaint, if the object has layer, this corresponds to
+  // PaintLayer::SubpixelAccumulation().
   paint_offset_translation = RoundedIntPoint(context_.current.paint_offset);
-  LayoutPoint fractional_paint_offset =
-      LayoutPoint(context_.current.paint_offset - *paint_offset_translation);
-  if (fractional_paint_offset != LayoutPoint()) {
-    // If the object has a non-translation transform, discard the fractional
-    // paint offset which can't be transformed by the transform.
-    TransformationMatrix matrix;
-    object_.StyleRef().ApplyTransform(
-        matrix, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
-        ComputedStyle::kIncludeMotionPath,
-        ComputedStyle::kIncludeIndependentTransformProperties);
-    if (!matrix.IsIdentityOrTranslation())
-      fractional_paint_offset = LayoutPoint();
+  LayoutPoint subpixel_accumulation;
+  // Don't propagate subpixel accumulation through paint isolation. In
+  // pre-CompositeAfterPaint we still need to keep consistence with the legacy
+  // compositing code.
+  if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
+      !NeedsIsolationNodes(object_)) {
+    subpixel_accumulation =
+        LayoutPoint(context_.current.paint_offset - *paint_offset_translation);
+    if (subpixel_accumulation != LayoutPoint()) {
+      // If the object has a non-translation transform, discard the fractional
+      // paint offset which can't be transformed by the transform.
+      TransformationMatrix matrix;
+      object_.StyleRef().ApplyTransform(
+          matrix, LayoutSize(), ComputedStyle::kExcludeTransformOrigin,
+          ComputedStyle::kIncludeMotionPath,
+          ComputedStyle::kIncludeIndependentTransformProperties);
+      if (!matrix.IsIdentityOrTranslation())
+        subpixel_accumulation = LayoutPoint();
+    }
   }
-  context_.current.paint_offset = fractional_paint_offset;
+  context_.current.paint_offset = subpixel_accumulation;
 }
 
 void FragmentPaintPropertyTreeBuilder::UpdatePaintOffsetTranslation(
@@ -2220,12 +2226,21 @@
   UpdatePaintOffset();
   UpdateForPaintOffsetTranslation(paint_offset_translation);
 
-  if (fragment_data_.PaintOffset() != context_.current.paint_offset) {
+  LayoutSize paint_offset_delta =
+      fragment_data_.PaintOffset() - context_.current.paint_offset;
+  if (!paint_offset_delta.IsZero()) {
     // Many paint properties depend on paint offset so we force an update of
-    // the entire subtree on paint offset changes.
-    // However, they are blocked by isolation.
-    full_context_.force_subtree_update_reasons |=
-        PaintPropertyTreeBuilderContext::kSubtreeUpdateIsolationBlocked;
+    // the entire subtree on paint offset changes. However, they are blocked by
+    // isolation if subpixel accumulation doesn't change or CompositeAfterPaint
+    // is enabled.
+    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
+        paint_offset_delta == RoundedIntSize(paint_offset_delta)) {
+      full_context_.force_subtree_update_reasons |=
+          PaintPropertyTreeBuilderContext::kSubtreeUpdateIsolationBlocked;
+    } else {
+      full_context_.force_subtree_update_reasons |=
+          PaintPropertyTreeBuilderContext::kSubtreeUpdateIsolationPiercing;
+    }
 
     object_.GetMutableForPainting().SetShouldCheckForPaintInvalidation();
     fragment_data_.SetPaintOffset(context_.current.paint_offset);
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index ad7a27b..ac41904 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -1578,4 +1578,45 @@
   // Pass if no crash.
 }
 
+TEST_P(PaintPropertyTreeUpdateTest, SubpixelAccumulationAcrossIsolation) {
+  SetBodyInnerHTML(R"HTML(
+    <style>body { margin: 0 }</style>
+    <div id="parent" style="margin-left: 10.25px">
+      <div id="isolation" style="contain: paint">
+        <div id="child"><div>
+      </div>
+    </div>
+  )HTML");
+  auto* parent_element = GetDocument().getElementById("parent");
+  auto* parent = parent_element->GetLayoutObject();
+  auto* isolation_properties = PaintPropertiesForElement("isolation");
+  auto* child = GetLayoutObjectByElementId("child");
+  EXPECT_EQ(LayoutPoint(LayoutUnit(10.25), LayoutUnit()),
+            parent->FirstFragment().PaintOffset());
+  EXPECT_EQ(FloatSize(10, 0), isolation_properties->PaintOffsetTranslation()
+                                  ->Matrix()
+                                  .To2DTranslation());
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    EXPECT_EQ(LayoutPoint(), child->FirstFragment().PaintOffset());
+  } else {
+    EXPECT_EQ(LayoutPoint(LayoutUnit(0.25), LayoutUnit()),
+              child->FirstFragment().PaintOffset());
+  }
+
+  parent_element->setAttribute(html_names::kStyleAttr, "margin-left: 12.75px");
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(LayoutPoint(LayoutUnit(12.75), LayoutUnit()),
+            parent->FirstFragment().PaintOffset());
+  EXPECT_EQ(FloatSize(13, 0), isolation_properties->PaintOffsetTranslation()
+                                  ->Matrix()
+                                  .To2DTranslation());
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    EXPECT_EQ(LayoutPoint(), child->FirstFragment().PaintOffset());
+  } else {
+    EXPECT_EQ(LayoutPoint(LayoutUnit(-0.25), LayoutUnit()),
+              child->FirstFragment().PaintOffset());
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/script/mock_script_element_base.h b/third_party/blink/renderer/core/script/mock_script_element_base.h
index bb7fea1..2afb66a 100644
--- a/third_party/blink/renderer/core/script/mock_script_element_base.h
+++ b/third_party/blink/renderer/core/script/mock_script_element_base.h
@@ -46,11 +46,10 @@
   MOCK_CONST_METHOD0(GetNonceForElement, const AtomicString&());
   MOCK_CONST_METHOD0(ElementHasDuplicateAttributes, bool());
   MOCK_CONST_METHOD0(InitiatorName, AtomicString());
-  MOCK_METHOD4(AllowInlineScriptForCSP,
+  MOCK_METHOD3(AllowInlineScriptForCSP,
                bool(const AtomicString&,
                     const WTF::OrdinalNumber&,
-                    const String&,
-                    ContentSecurityPolicy::InlineType));
+                    const String&));
   MOCK_CONST_METHOD0(GetDocument, Document&());
   MOCK_METHOD1(SetScriptElementForBinding,
                void(HTMLScriptElementOrSVGScriptElement&));
diff --git a/third_party/blink/renderer/core/script/pending_script.cc b/third_party/blink/renderer/core/script/pending_script.cc
index a4692e92..e309af8 100644
--- a/third_party/blink/renderer/core/script/pending_script.cc
+++ b/third_party/blink/renderer/core/script/pending_script.cc
@@ -160,9 +160,8 @@
 
     AtomicString nonce = element_->GetNonceForElement();
     if (!should_bypass_main_world_csp &&
-        !element_->AllowInlineScriptForCSP(
-            nonce, StartingPosition().line_, script->InlineSourceTextForCSP(),
-            ContentSecurityPolicy::InlineType::kBlock)) {
+        !element_->AllowInlineScriptForCSP(nonce, StartingPosition().line_,
+                                           script->InlineSourceTextForCSP())) {
       // Consider as if:
       //
       // <spec step="2">If the script's script is null, ...</spec>
diff --git a/third_party/blink/renderer/core/script/script_element_base.h b/third_party/blink/renderer/core/script/script_element_base.h
index 23e974d..4c589aa 100644
--- a/third_party/blink/renderer/core/script/script_element_base.h
+++ b/third_party/blink/renderer/core/script/script_element_base.h
@@ -22,7 +22,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_SCRIPT_SCRIPT_ELEMENT_BASE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
@@ -62,8 +61,7 @@
 
   virtual bool AllowInlineScriptForCSP(const AtomicString& nonce,
                                        const WTF::OrdinalNumber&,
-                                       const String& script_content,
-                                       ContentSecurityPolicy::InlineType) = 0;
+                                       const String& script_content) = 0;
   virtual Document& GetDocument() const = 0;
   virtual void SetScriptElementForBinding(
       HTMLScriptElementOrSVGScriptElement&) = 0;
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator.cc b/third_party/blink/renderer/core/scroll/scroll_animator.cc
index 2eeecfa4..ad4fc61 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.cc
@@ -171,6 +171,16 @@
       animation_curve_->UpdateTarget(
           TimeDelta::FromSecondsD(time_function_() - start_time_),
           CompositorOffsetFromBlinkOffset(target_offset));
+
+      // Schedule an animation for this scrollable area even though we are
+      // updating the animation target - updating the animation will keep
+      // it going for another frame. This typically will happen at the
+      // beginning of a frame when coalesced input is dispatched.
+      // If we don't schedule an animation during the handling of the input
+      // event, the LatencyInfo associated with the input event will not be
+      // added as a swap promise and we won't get any swap results.
+      GetScrollableArea()->ScheduleAnimation();
+
       return true;
     }
 
diff --git a/third_party/blink/renderer/core/svg/svg_script_element.cc b/third_party/blink/renderer/core/svg/svg_script_element.cc
index fbd263c..fd81c48 100644
--- a/third_party/blink/renderer/core/svg/svg_script_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_script_element.cc
@@ -136,11 +136,9 @@
 bool SVGScriptElement::AllowInlineScriptForCSP(
     const AtomicString& nonce,
     const WTF::OrdinalNumber& context_line,
-    const String& script_content,
-    ContentSecurityPolicy::InlineType inline_type) {
+    const String& script_content) {
   return GetDocument().GetContentSecurityPolicy()->AllowInlineScript(
-      this, GetDocument().Url(), nonce, context_line, script_content,
-      inline_type);
+      this, GetDocument().Url(), nonce, context_line, script_content);
 }
 
 Document& SVGScriptElement::GetDocument() const {
diff --git a/third_party/blink/renderer/core/svg/svg_script_element.h b/third_party/blink/renderer/core/svg/svg_script_element.h
index 669ce8e27..8f472203 100644
--- a/third_party/blink/renderer/core/svg/svg_script_element.h
+++ b/third_party/blink/renderer/core/svg/svg_script_element.h
@@ -91,8 +91,7 @@
   }
   bool AllowInlineScriptForCSP(const AtomicString& nonce,
                                const WTF::OrdinalNumber&,
-                               const String& script_content,
-                               ContentSecurityPolicy::InlineType) override;
+                               const String& script_content) override;
   Document& GetDocument() const override;
   void DispatchLoadEvent() override;
   void DispatchErrorEvent() override;
diff --git a/third_party/blink/renderer/devtools/front_end/resources/ClearStorageView.js b/third_party/blink/renderer/devtools/front_end/resources/ClearStorageView.js
index b164d80..fc516c5 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/ClearStorageView.js
+++ b/third_party/blink/renderer/devtools/front_end/resources/ClearStorageView.js
@@ -183,6 +183,20 @@
         appcacheModel.reset();
     }
 
+    if (set.has(Protocol.Storage.StorageType.Service_workers) || hasAll) {
+      for (const serviceWorkerManager of SDK.targetManager.models(SDK.ServiceWorkerManager)) {
+        const securityOriginManager = serviceWorkerManager.target().model(SDK.SecurityOriginManager);
+        for (const registration of serviceWorkerManager.registrations().values()) {
+          if (!securityOriginManager.securityOrigins().includes(registration.securityOrigin))
+            continue;
+          const activeVersion = registration.versionsByMode().get(SDK.ServiceWorkerVersion.Modes.Active);
+          if (!activeVersion)
+            continue;
+          serviceWorkerManager.stopWorker(activeVersion.id);
+        }
+      }
+    }
+
     this._clearButton.disabled = true;
     const label = this._clearButton.textContent;
     this._clearButton.textContent = Common.UIString('Clearing...');
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index a7e112f..691669f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -370,17 +370,26 @@
   // Then we clip the bounding box to the canvas visible range.
   path_rect.Intersect(canvas_rect);
 
+  // Horizontal text is aligned at the top of the screen
+  ScrollAlignment horizontal_scroll_mode =
+      ScrollAlignment::kAlignToEdgeIfNeeded;
+  ScrollAlignment vertical_scroll_mode = ScrollAlignment::kAlignTopAlways;
+
+  // Vertical text needs be aligned horizontally on the screen
   bool is_horizontal_writing_mode =
       canvas()->EnsureComputedStyle()->IsHorizontalWritingMode();
-
+  if (!is_horizontal_writing_mode) {
+    bool is_right_to_left =
+        canvas()->EnsureComputedStyle()->IsFlippedBlocksWritingMode();
+    horizontal_scroll_mode =
+        (is_right_to_left ? ScrollAlignment::kAlignRightAlways
+                          : ScrollAlignment::kAlignLeftAlways);
+    vertical_scroll_mode = ScrollAlignment::kAlignToEdgeIfNeeded;
+  }
   renderer->ScrollRectToVisible(
       path_rect,
-      WebScrollIntoViewParams(
-          is_horizontal_writing_mode ? ScrollAlignment::kAlignToEdgeIfNeeded
-                                     : ScrollAlignment::kAlignLeftAlways,
-          !is_horizontal_writing_mode ? ScrollAlignment::kAlignToEdgeIfNeeded
-                                      : ScrollAlignment::kAlignTopAlways,
-          kProgrammaticScroll, false, kScrollBehaviorAuto));
+      WebScrollIntoViewParams(horizontal_scroll_mode, vertical_scroll_mode,
+                              kProgrammaticScroll, false, kScrollBehaviorAuto));
 }
 
 void CanvasRenderingContext2D::clearRect(double x,
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
index 840302c2..8253c607 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.idl
@@ -125,10 +125,10 @@
 
     // pixel manipulation
     [RaisesException] ImageData createImageData(ImageData imagedata);
-    [RaisesException] ImageData createImageData(long sw, long sh);
-    [RaisesException] ImageData getImageData(long sx, long sy, long sw, long sh);
-    [RaisesException] void putImageData(ImageData imagedata, long dx, long dy);
-    [RaisesException] void putImageData(ImageData imagedata, long dx, long dy, long dirtyX, long dirtyY, long dirtyWidth, long dirtyHeight);
+    [RaisesException] ImageData createImageData([EnforceRange] long sw, [EnforceRange] long sh);
+    [RaisesException] ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh);
+    [RaisesException] void putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy);
+    [RaisesException] void putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight);
 
     // https://github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md
     [RuntimeEnabled=CanvasColorManagement, RaisesException] ImageData createImageData(unsigned long sw, unsigned long sh, ImageDataColorSettings imageDataColorSettings);
diff --git a/third_party/blink/renderer/modules/filesystem/BUILD.gn b/third_party/blink/renderer/modules/filesystem/BUILD.gn
index 02153462..2bfc4ebc 100644
--- a/third_party/blink/renderer/modules/filesystem/BUILD.gn
+++ b/third_party/blink/renderer/modules/filesystem/BUILD.gn
@@ -6,6 +6,7 @@
 
 blink_modules_sources("filesystem") {
   sources = [
+    "async_file_system_callbacks.h",
     "data_transfer_item_file_system.cc",
     "data_transfer_item_file_system.h",
     "dev_tools_host_file_system.cc",
diff --git a/third_party/blink/renderer/platform/async_file_system_callbacks.h b/third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h
similarity index 93%
rename from third_party/blink/renderer/platform/async_file_system_callbacks.h
rename to third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h
index 875ca6db..e897164 100644
--- a/third_party/blink/renderer/platform/async_file_system_callbacks.h
+++ b/third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h
@@ -28,8 +28,8 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_FILESYSTEM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_FILESYSTEM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
 
 #include <memory>
 
@@ -101,4 +101,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_FILESYSTEM_ASYNC_FILE_SYSTEM_CALLBACKS_H_
diff --git a/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc b/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
index 4a2b0fd6..c1690f0 100644
--- a/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
+++ b/third_party/blink/renderer/modules/filesystem/data_transfer_item_file_system.cc
@@ -35,13 +35,13 @@
 #include "third_party/blink/renderer/core/clipboard/data_transfer_item.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fileapi/file.h"
+#include "third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/modules/filesystem/directory_entry.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_path.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_system.h"
 #include "third_party/blink/renderer/modules/filesystem/dragged_isolated_file_system_impl.h"
 #include "third_party/blink/renderer/modules/filesystem/entry.h"
 #include "third_party/blink/renderer/modules/filesystem/file_entry.h"
-#include "third_party/blink/renderer/platform/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/file_metadata.h"
 
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_callbacks.h b/third_party/blink/renderer/modules/filesystem/file_system_callbacks.h
index 5654f88..1a8ba11 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_callbacks.h
+++ b/third_party/blink/renderer/modules/filesystem/file_system_callbacks.h
@@ -42,8 +42,8 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_file_writer_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_metadata_callback.h"
 #include "third_party/blink/renderer/core/fileapi/file_error.h"
+#include "third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/modules/filesystem/entry_heap_vector.h"
-#include "third_party/blink/renderer/platform/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
diff --git a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
index 163d0db1..79ca642 100644
--- a/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
+++ b/third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h
@@ -10,7 +10,7 @@
 #include "mojo/public/cpp/bindings/strong_binding_set.h"
 #include "third_party/blink/public/mojom/filesystem/file_system.mojom-blink.h"
 #include "third_party/blink/public/platform/web_callbacks.h"
-#include "third_party/blink/renderer/platform/async_file_system_callbacks.h"
+#include "third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 
diff --git a/third_party/blink/renderer/modules/filesystem/local_file_system.cc b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
index 120dee9..312e2c0 100644
--- a/third_party/blink/renderer/modules/filesystem/local_file_system.cc
+++ b/third_party/blink/renderer/modules/filesystem/local_file_system.cc
@@ -45,10 +45,10 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
+#include "third_party/blink/renderer/modules/filesystem/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/modules/filesystem/dom_file_system.h"
 #include "third_party/blink/renderer/modules/filesystem/file_system_client.h"
 #include "third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h"
-#include "third_party/blink/renderer/platform/async_file_system_callbacks.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad.idl b/third_party/blink/renderer/modules/gamepad/gamepad.idl
index b34667be..574e520 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad.idl
+++ b/third_party/blink/renderer/modules/gamepad/gamepad.idl
@@ -41,7 +41,7 @@
 
     // Gamepad vibration is proposed as an extension to the Gamepad API.
     // https://github.com/w3c/gamepad/pull/68
-    [RuntimeEnabled=GamepadVibration, MeasureAs=GamepadVibrationActuator] readonly attribute GamepadHapticActuator? vibrationActuator;
+    [MeasureAs=GamepadVibrationActuator] readonly attribute GamepadHapticActuator? vibrationActuator;
 
     [RuntimeEnabled=WebVRGamepadSupport, MeasureAs=GamepadPose] readonly attribute GamepadPose? pose;
     [OriginTrialEnabled=WebXRGamepadSupport, MeasureAs=GamepadHand] readonly attribute GamepadHand hand;
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_haptic_actuator.idl b/third_party/blink/renderer/modules/gamepad/gamepad_haptic_actuator.idl
index e3b410f..e50d9f9 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_haptic_actuator.idl
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_haptic_actuator.idl
@@ -21,9 +21,7 @@
 
 // Gamepad vibration is proposed as an extension to the Gamepad API.
 // https://github.com/w3c/gamepad/pull/68
-[
-    RuntimeEnabled=GamepadVibration
-] interface GamepadHapticActuator {
+interface GamepadHapticActuator {
     readonly attribute GamepadHapticActuatorType type;
     [CallWith=ScriptState] Promise<GamepadHapticsResult> playEffect(
         GamepadHapticEffectType type,
diff --git a/third_party/blink/renderer/modules/idle/idle_manager.cc b/third_party/blink/renderer/modules/idle/idle_manager.cc
index c20d66e..f3edf0ac 100644
--- a/third_party/blink/renderer/modules/idle/idle_manager.cc
+++ b/third_party/blink/renderer/modules/idle/idle_manager.cc
@@ -36,6 +36,8 @@
     return ScriptPromise();
   }
 
+  base::TimeDelta threshold = base::TimeDelta::FromSeconds(threshold_seconds);
+
   // TODO: Permission check.
 
   if (!service_) {
@@ -51,12 +53,12 @@
 
   mojom::blink::IdleMonitorPtr monitor_ptr;
   IdleStatus* status =
-      IdleStatus::Create(ExecutionContext::From(script_state),
-                         threshold_seconds, mojo::MakeRequest(&monitor_ptr));
+      IdleStatus::Create(ExecutionContext::From(script_state), threshold,
+                         mojo::MakeRequest(&monitor_ptr));
 
   requests_.insert(resolver);
   service_->AddMonitor(
-      threshold_seconds, std::move(monitor_ptr),
+      threshold, std::move(monitor_ptr),
       WTF::Bind(&IdleManager::OnAddMonitor, WrapPersistent(this),
                 WrapPersistent(resolver), WrapPersistent(status)));
 
diff --git a/third_party/blink/renderer/modules/idle/idle_status.cc b/third_party/blink/renderer/modules/idle/idle_status.cc
index 463871eb..c9afa32 100644
--- a/third_party/blink/renderer/modules/idle/idle_status.cc
+++ b/third_party/blink/renderer/modules/idle/idle_status.cc
@@ -18,7 +18,7 @@
 namespace blink {
 
 IdleStatus* IdleStatus::Create(ExecutionContext* context,
-                               uint32_t threshold,
+                               base::TimeDelta threshold,
                                mojom::blink::IdleMonitorRequest request) {
   auto* status =
       MakeGarbageCollected<IdleStatus>(context, threshold, std::move(request));
@@ -27,7 +27,7 @@
 }
 
 IdleStatus::IdleStatus(ExecutionContext* context,
-                       uint32_t threshold,
+                       base::TimeDelta threshold,
                        mojom::blink::IdleMonitorRequest request)
     : ContextLifecycleStateObserver(context),
       threshold_(threshold),
diff --git a/third_party/blink/renderer/modules/idle/idle_status.h b/third_party/blink/renderer/modules/idle/idle_status.h
index 4d9f46ca..a680904 100644
--- a/third_party/blink/renderer/modules/idle/idle_status.h
+++ b/third_party/blink/renderer/modules/idle/idle_status.h
@@ -32,11 +32,11 @@
   // to script until the monitor has been registered by the service and
   // returned an initial state.
   static IdleStatus* Create(ExecutionContext* context,
-                            uint32_t threshold,
+                            base::TimeDelta threshold,
                             mojom::blink::IdleMonitorRequest request);
 
   IdleStatus(ExecutionContext*,
-             uint32_t threshold,
+             base::TimeDelta threshold,
              mojom::blink::IdleMonitorRequest);
   ~IdleStatus() override;
   void Dispose();
@@ -76,7 +76,7 @@
 
   Member<blink::IdleState> state_;
 
-  const uint32_t threshold_;
+  const base::TimeDelta threshold_;
 
   // Holds a pipe which the service uses to notify this object
   // when the idle state has changed.
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
index 14ed7d3..45bf4c0 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
@@ -938,7 +938,9 @@
 
   UpdatePlayState();
 
-  UpdateTimeIndicators();
+  UpdateCurrentTimeDisplay();
+
+  timeline_->SetPosition(MediaElement().currentTime());
 
   OnVolumeChange();
   OnTextTracksAddedOrRemoved();
@@ -953,11 +955,6 @@
   OnControlsListUpdated();
 }
 
-void MediaControlsImpl::UpdateTimeIndicators() {
-  timeline_->SetPosition(MediaElement().currentTime());
-  UpdateCurrentTimeDisplay();
-}
-
 void MediaControlsImpl::OnControlsListUpdated() {
   BatchedControlUpdate batch(this);
 
@@ -1128,9 +1125,6 @@
   if (panel_->KeepDisplayedForAccessibility())
     return false;
 
-  if (MediaElement().seeking())
-    return false;
-
   return true;
 }
 
@@ -1626,7 +1620,8 @@
       is_mouse_over_controls_ = true;
       if (!MediaElement().paused()) {
         MakeOpaqueFromPointerEvent();
-        StartHideMediaControlsIfNecessary();
+        if (ShouldHideMediaControls())
+          StartHideMediaControlsTimer();
       }
     }
   } else if (event->type() == event_type_names::kPointerout) {
@@ -1809,11 +1804,6 @@
   overlay_cast_button_->SetIsWanted(false);
 }
 
-void MediaControlsImpl::StartHideMediaControlsIfNecessary() {
-  if (ShouldHideMediaControls())
-    StartHideMediaControlsTimer();
-}
-
 void MediaControlsImpl::StartHideMediaControlsTimer() {
   hide_media_controls_timer_.StartOneShot(
       GetTimeWithoutMouseMovementBeforeHidingMediaControls(), FROM_HERE);
@@ -1887,7 +1877,8 @@
 }
 
 void MediaControlsImpl::OnTimeUpdate() {
-  UpdateTimeIndicators();
+  timeline_->SetPosition(MediaElement().currentTime());
+  UpdateCurrentTimeDisplay();
 
   // 'timeupdate' might be called in a paused state. The controls should not
   // become transparent in that case.
@@ -1929,7 +1920,8 @@
 
 void MediaControlsImpl::OnPlay() {
   UpdatePlayState();
-  UpdateTimeIndicators();
+  timeline_->SetPosition(MediaElement().currentTime());
+  UpdateCurrentTimeDisplay();
   UpdateCSSClassFromState();
 }
 
@@ -1942,7 +1934,8 @@
 
 void MediaControlsImpl::OnPause() {
   UpdatePlayState();
-  UpdateTimeIndicators();
+  timeline_->SetPosition(MediaElement().currentTime());
+  UpdateCurrentTimeDisplay();
   MakeOpaque();
 
   StopHideMediaControlsTimer();
@@ -1950,32 +1943,6 @@
   UpdateCSSClassFromState();
 }
 
-void MediaControlsImpl::OnSeeking() {
-  UpdateTimeIndicators();
-  if (!is_scrubbing_) {
-    is_scrubbing_ = true;
-    UpdateCSSClassFromState();
-  }
-
-  // Don't try to show the controls if the seek was caused by the video being
-  // looped.
-  if (MediaElement().Loop() && MediaElement().currentTime() == 0)
-    return;
-
-  if (!MediaElement().ShouldShowControls())
-    return;
-
-  MaybeShow();
-  StopHideMediaControlsTimer();
-}
-
-void MediaControlsImpl::OnSeeked() {
-  StartHideMediaControlsIfNecessary();
-
-  is_scrubbing_ = false;
-  UpdateCSSClassFromState();
-}
-
 void MediaControlsImpl::OnTextTracksAddedOrRemoved() {
   toggle_closed_captions_button_->UpdateDisplayType();
   toggle_closed_captions_button_->SetIsWanted(
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.h b/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
index 6766552..d3017292 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
@@ -282,9 +282,6 @@
 
   void ElementSizeChangedTimerFired(TimerBase*);
 
-  // Update any visible indicators of the current time.
-  void UpdateTimeIndicators();
-
   // Hide elements that don't fit, and show those things that we want which
   // do fit.  This requires that m_effectiveWidth and m_effectiveHeight are
   // current.
@@ -341,8 +338,6 @@
   void OnPlay();
   void OnPlaying();
   void OnPause();
-  void OnSeeking();
-  void OnSeeked();
   void OnTextTracksAddedOrRemoved();
   void OnTextTracksChanged();
   void OnError();
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index 3b97ac8..0b92a45 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -222,9 +222,6 @@
 
   void SimulateLoadedMetadata() { media_controls_->OnLoadedMetadata(); }
 
-  void SimulateOnSeeking() { media_controls_->OnSeeking(); }
-  void SimulateOnSeeked() { media_controls_->OnSeeked(); }
-
   MediaControlsImpl& MediaControls() { return *media_controls_; }
   MediaControlVolumeSliderElement* VolumeSliderElement() const {
     return media_controls_->volume_slider_;
@@ -627,32 +624,6 @@
   EXPECT_EQ(duration / 2, current_time_display->CurrentValue());
 }
 
-TEST_F(MediaControlsImplTest, TimeIndicatorsUpdatedOnSeeking) {
-  EnsureSizing();
-
-  MediaControlCurrentTimeDisplayElement* current_time_display =
-      GetCurrentTimeDisplayElement();
-  MediaControlTimelineElement* timeline = TimelineElement();
-  double duration = 1000;
-  LoadMediaWithDuration(duration);
-
-  EXPECT_EQ(0, current_time_display->CurrentValue());
-  EXPECT_EQ(0, timeline->valueAsNumber());
-
-  MediaControls().MediaElement().setCurrentTime(duration / 4);
-
-  // Time indicators are not yet updated.
-  EXPECT_EQ(0, current_time_display->CurrentValue());
-  EXPECT_EQ(0, timeline->valueAsNumber());
-
-  SimulateOnSeeking();
-
-  // The time indicators should be updated immediately when the 'seeking' event
-  // is fired.
-  EXPECT_EQ(duration / 4, current_time_display->CurrentValue());
-  EXPECT_EQ(duration / 4, timeline->valueAsNumber());
-}
-
 TEST_F(MediaControlsImplTest, TimelineMetricsWidth) {
   MediaControls().MediaElement().SetSrc("https://example.com/foo.mp4");
   test::RunPendingTasks();
@@ -892,45 +863,6 @@
 
 }  // namespace
 
-TEST_F(MediaControlsImplTestWithMockScheduler, SeekingShowsControls) {
-  Element* panel = GetElementByShadowPseudoId(MediaControls(),
-                                              "-webkit-media-controls-panel");
-  ASSERT_NE(nullptr, panel);
-
-  MediaControls().MediaElement().SetSrc("http://example.com");
-  MediaControls().MediaElement().Play();
-
-  // Hide the controls to start.
-  MediaControls().Hide();
-  EXPECT_FALSE(IsElementVisible(*panel));
-
-  // Seeking should cause the controls to become visible.
-  SimulateOnSeeking();
-  EXPECT_TRUE(IsElementVisible(*panel));
-}
-
-TEST_F(MediaControlsImplTestWithMockScheduler,
-       SeekingDoesNotShowControlsWhenNoControlsAttr) {
-  Element* panel = GetElementByShadowPseudoId(MediaControls(),
-                                              "-webkit-media-controls-panel");
-  ASSERT_NE(nullptr, panel);
-
-  MediaControls().MediaElement().SetBooleanAttribute(html_names::kControlsAttr,
-                                                     false);
-
-  MediaControls().MediaElement().SetSrc("http://example.com");
-  MediaControls().MediaElement().Play();
-
-  // Hide the controls to start.
-  MediaControls().Hide();
-  EXPECT_FALSE(IsElementVisible(*panel));
-
-  // Seeking should not cause the controls to become visible because the
-  // controls attribute is not set.
-  SimulateOnSeeking();
-  EXPECT_FALSE(IsElementVisible(*panel));
-}
-
 TEST_F(MediaControlsImplTestWithMockScheduler,
        ControlsRemainVisibleDuringKeyboardInteraction) {
   EnsureSizing();
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc b/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
index 0fe65f1f..3581220 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
@@ -36,8 +36,6 @@
   GetMediaElement().addEventListener(event_type_names::kPause, this, false);
   GetMediaElement().addEventListener(event_type_names::kDurationchange, this,
                                      false);
-  GetMediaElement().addEventListener(event_type_names::kSeeking, this, false);
-  GetMediaElement().addEventListener(event_type_names::kSeeked, this, false);
   GetMediaElement().addEventListener(event_type_names::kError, this, false);
   GetMediaElement().addEventListener(event_type_names::kLoadedmetadata, this,
                                      false);
@@ -179,14 +177,6 @@
     media_controls_->OnPause();
     return;
   }
-  if (event->type() == event_type_names::kSeeking) {
-    media_controls_->OnSeeking();
-    return;
-  }
-  if (event->type() == event_type_names::kSeeked) {
-    media_controls_->OnSeeked();
-    return;
-  }
   if (event->type() == event_type_names::kError) {
     media_controls_->OnError();
     return;
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index d5c8be36..39b7e9e 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -33,9 +33,6 @@
 const char kActiveImmersiveSession[] =
     "There is already an active, immersive XRSession.";
 
-const char kNoOutputContext[] =
-    "Inline sessions must be created with an outputContext.";
-
 const char kRequestRequiresUserActivation[] =
     "The requested session requires user activation.";
 
@@ -65,7 +62,6 @@
 
 void XR::PendingSessionQuery::Trace(blink::Visitor* visitor) {
   visitor->Trace(resolver);
-  visitor->Trace(output_context);
 }
 
 XR::XR(LocalFrame& frame, int64_t ukm_source_id)
@@ -115,19 +111,6 @@
   return environment_provider_;
 }
 
-const char* XR::checkSessionSupport(
-    const XRSessionCreationOptions* options) const {
-  if (options->mode() == "inline" || options->mode() == "legacy-inline-ar") {
-    // Validation for inline sessions. (Validation for immersive sessions
-    // happens browser-side.)
-    if (!options->hasOutputContext()) {
-      return kNoOutputContext;
-    }
-  }
-
-  return nullptr;
-}
-
 ScriptPromise XR::supportsSessionMode(ScriptState* script_state,
                                       const String& mode) {
   LocalFrame* frame = GetFrame();
@@ -226,15 +209,6 @@
                                            kNoDevicesMessage));
   }
 
-  // Check first to see if the device is capable of supporting the requested
-  // options.
-  const char* reject_reason = checkSessionSupport(options);
-  if (reject_reason) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state, DOMException::Create(DOMExceptionCode::kNotSupportedError,
-                                           reject_reason));
-  }
-
   // TODO(ijamardo): Should we just exit if there is not document?
   bool has_user_activation =
       LocalFrame::HasTransientUserActivation(doc ? doc->GetFrame() : nullptr);
@@ -274,8 +248,6 @@
 
   PendingSessionQuery* query = MakeGarbageCollected<PendingSessionQuery>(
       resolver, XRSession::stringToSessionMode(options->mode()));
-  query->output_context =
-      options->hasOutputContext() ? options->outputContext() : nullptr;
   query->has_user_activation = has_user_activation;
 
   if (!device_) {
@@ -295,7 +267,7 @@
     if (query->mode == XRSession::kModeInline) {
       XRSession* session = MakeGarbageCollected<XRSession>(
           this, nullptr /* client request */, query->mode,
-          query->output_context, XRSession::kBlendModeOpaque);
+          XRSession::kBlendModeOpaque);
       sessions_.insert(session);
       query->resolver->Resolve(session);
       return;
@@ -419,8 +391,7 @@
     blend_mode = XRSession::kBlendModeAlphaBlend;
 
   XRSession* session = MakeGarbageCollected<XRSession>(
-      this, std::move(session_ptr->client_request), query->mode,
-      query->output_context, blend_mode);
+      this, std::move(session_ptr->client_request), query->mode, blend_mode);
   session->SetXRDisplayInfo(std::move(session_ptr->display_info));
   sessions_.insert(session);
 
diff --git a/third_party/blink/renderer/modules/xr/xr.h b/third_party/blink/renderer/modules/xr/xr.h
index 945c14c..7a936861 100644
--- a/third_party/blink/renderer/modules/xr/xr.h
+++ b/third_party/blink/renderer/modules/xr/xr.h
@@ -84,12 +84,9 @@
 
     Member<ScriptPromiseResolver> resolver;
     const XRSession::SessionMode mode;
-    Member<XRPresentationContext> output_context;
     bool has_user_activation = false;
   };
 
-  const char* checkSessionSupport(const XRSessionCreationOptions*) const;
-
   void OnRequestDeviceReturned(device::mojom::blink::XRDevicePtr device);
   void DispatchPendingSessionCalls();
 
diff --git a/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.h b/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.h
index cd4f219d..7811f71 100644
--- a/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.h
+++ b/third_party/blink/renderer/modules/xr/xr_canvas_input_provider.h
@@ -28,6 +28,7 @@
   virtual ~XRCanvasInputProvider();
 
   XRSession* session() const { return session_; }
+  HTMLCanvasElement* canvas() const { return canvas_; }
 
   // Remove all event listeners.
   void Stop();
diff --git a/third_party/blink/renderer/modules/xr/xr_presentation_context.cc b/third_party/blink/renderer/modules/xr/xr_presentation_context.cc
index 9944e79..4acd354 100644
--- a/third_party/blink/renderer/modules/xr/xr_presentation_context.cc
+++ b/third_party/blink/renderer/modules/xr/xr_presentation_context.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/bindings/modules/v8/rendering_context.h"
 #include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
+#include "third_party/blink/renderer/modules/xr/xr_session.h"
 
 namespace blink {
 
@@ -29,4 +30,17 @@
   return MakeGarbageCollected<XRPresentationContext>(host, attrs);
 }
 
+void XRPresentationContext::BindToSession(XRSession* session) {
+  if (bound_session_) {
+    bound_session_->DetachOutputContext(this);
+  }
+
+  bound_session_ = session;
+}
+
+void XRPresentationContext::Trace(blink::Visitor* visitor) {
+  visitor->Trace(bound_session_);
+  ImageBitmapRenderingContextBase::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_presentation_context.h b/third_party/blink/renderer/modules/xr/xr_presentation_context.h
index f8b9ceca..ef0ec2c3 100644
--- a/third_party/blink/renderer/modules/xr/xr_presentation_context.h
+++ b/third_party/blink/renderer/modules/xr/xr_presentation_context.h
@@ -12,6 +12,8 @@
 
 namespace blink {
 
+class XRSession;
+
 class MODULES_EXPORT XRPresentationContext final
     : public ImageBitmapRenderingContextBase {
   DEFINE_WRAPPERTYPEINFO();
@@ -41,6 +43,13 @@
   XRPresentationContext(CanvasRenderingContextHost*,
                         const CanvasContextCreationAttributesCore&);
   ~XRPresentationContext() override;
+
+  void BindToSession(XRSession*);
+
+  void Trace(blink::Visitor*) override;
+
+ private:
+  Member<XRSession> bound_session_;
 };
 
 DEFINE_TYPE_CASTS(XRPresentationContext,
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state.cc b/third_party/blink/renderer/modules/xr/xr_render_state.cc
index 939058bf..f07f823 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state.cc
+++ b/third_party/blink/renderer/modules/xr/xr_render_state.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/xr/xr_render_state.h"
 
 #include "third_party/blink/renderer/modules/xr/xr_layer.h"
+#include "third_party/blink/renderer/modules/xr/xr_presentation_context.h"
 #include "third_party/blink/renderer/modules/xr/xr_render_state_init.h"
 
 namespace blink {
@@ -19,10 +20,18 @@
   if (init->hasBaseLayer()) {
     base_layer_ = init->baseLayer();
   }
+  if (init->hasOutputContext()) {
+    output_context_ = init->outputContext();
+  }
+}
+
+void XRRenderState::removeOutputContext() {
+  output_context_ = nullptr;
 }
 
 void XRRenderState::Trace(blink::Visitor* visitor) {
   visitor->Trace(base_layer_);
+  visitor->Trace(output_context_);
   ScriptWrappable::Trace(visitor);
 }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state.h b/third_party/blink/renderer/modules/xr/xr_render_state.h
index aa7161b..d39d88c 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state.h
+++ b/third_party/blink/renderer/modules/xr/xr_render_state.h
@@ -11,6 +11,7 @@
 namespace blink {
 
 class XRLayer;
+class XRPresentationContext;
 class XRRenderStateInit;
 
 class XRRenderState : public ScriptWrappable {
@@ -25,15 +26,21 @@
   double depthNear() const { return depth_near_; }
   double depthFar() const { return depth_far_; }
   XRLayer* baseLayer() const { return base_layer_; }
+  XRPresentationContext* outputContext() const { return output_context_; }
 
   void Update(const XRRenderStateInit* init);
 
+  // Only used when removing an outputContext from the session because it was
+  // bound to a different session.
+  void removeOutputContext();
+
   void Trace(blink::Visitor*) override;
 
  private:
   double depth_near_ = 0.1;
   double depth_far_ = 1000.0;
   Member<XRLayer> base_layer_;
+  Member<XRPresentationContext> output_context_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state.idl b/third_party/blink/renderer/modules/xr/xr_render_state.idl
index c6539d3..0eea405 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state.idl
+++ b/third_party/blink/renderer/modules/xr/xr_render_state.idl
@@ -11,4 +11,5 @@
   readonly attribute double depthNear;
   readonly attribute double depthFar;
   readonly attribute XRLayer? baseLayer;
+  readonly attribute XRPresentationContext? outputContext;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_render_state_init.idl b/third_party/blink/renderer/modules/xr/xr_render_state_init.idl
index c568068..5a955735 100644
--- a/third_party/blink/renderer/modules/xr/xr_render_state_init.idl
+++ b/third_party/blink/renderer/modules/xr/xr_render_state_init.idl
@@ -7,4 +7,5 @@
   double depthNear;
   double depthFar;
   XRLayer? baseLayer;
+  XRPresentationContext? outputContext;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index e269334..6c9a30a 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -139,14 +139,12 @@
     XR* xr,
     device::mojom::blink::XRSessionClientRequest client_request,
     XRSession::SessionMode mode,
-    XRPresentationContext* output_context,
     EnvironmentBlendMode environment_blend_mode)
     : xr_(xr),
       mode_(mode),
       mode_string_(sessionModeToString(mode)),
       environment_integration_(mode == kModeInlineAR ||
                                mode == kModeImmersiveAR),
-      output_context_(output_context),
       client_binding_(this, std::move(client_request)),
       callback_collection_(
           MakeGarbageCollected<XRFrameRequestCallbackCollection>(
@@ -154,26 +152,6 @@
   render_state_ = MakeGarbageCollected<XRRenderState>();
   blurred_ = !HasAppropriateFocus();
 
-  // When an output context is provided, monitor it for resize events.
-  if (output_context_) {
-    HTMLCanvasElement* canvas = outputContext()->canvas();
-    if (canvas) {
-      resize_observer_ = ResizeObserver::Create(
-          canvas->GetDocument(),
-          MakeGarbageCollected<XRSessionResizeObserverDelegate>(this));
-      resize_observer_->observe(canvas);
-
-      // Begin processing input events on the output context's canvas.
-      if (!immersive()) {
-        canvas_input_provider_ =
-            MakeGarbageCollected<XRCanvasInputProvider>(this, canvas);
-      }
-
-      // Get the initial canvas dimensions
-      UpdateCanvasDimensions(canvas);
-    }
-  }
-
   switch (environment_blend_mode) {
     case kBlendModeOpaque:
       blend_mode_string_ = "opaque";
@@ -238,6 +216,17 @@
     }
   }
 
+  if (!immersive() && init->hasOutputContext() && init->outputContext()) {
+    // If the outputContext was previously null and there are outstanding rAF
+    // callbacks, kick off a new frame request to flush them out.
+    if (!render_state_->outputContext() && !pending_frame_ &&
+        !callback_collection_->IsEmpty()) {
+      // Kick off a request for a new XR frame.
+      xr_->frameProvider()->RequestFrame(this);
+      pending_frame_ = true;
+    }
+  }
+
   pending_render_state_.push_back(init);
 }
 
@@ -505,13 +494,17 @@
 }
 
 DoubleSize XRSession::OutputCanvasSize() const {
-  if (!output_context_) {
+  if (!render_state_->outputContext()) {
     return DoubleSize();
   }
 
   return DoubleSize(output_width_, output_height_);
 }
 
+XRPresentationContext* XRSession::outputContext() const {
+  return render_state_->outputContext();
+}
+
 void XRSession::OnFocus() {
   if (!blurred_)
     return;
@@ -543,6 +536,79 @@
   }
 }
 
+void XRSession::DetachOutputContext(XRPresentationContext* output_context) {
+  if (!output_context)
+    return;
+
+  // Remove anything in this session observing the given output context.
+  HTMLCanvasElement* canvas = output_context->canvas();
+  if (canvas) {
+    if (resize_observer_) {
+      resize_observer_->unobserve(canvas);
+    }
+
+    if (canvas_input_provider_ && canvas_input_provider_->canvas() == canvas) {
+      canvas_input_provider_->Stop();
+      canvas_input_provider_ = nullptr;
+    }
+  }
+
+  if (render_state_->outputContext() == output_context) {
+    render_state_->removeOutputContext();
+  }
+}
+
+void XRSession::ApplyPendingRenderState() {
+  if (pending_render_state_.size() > 0) {
+    XRLayer* prev_base_layer = render_state_->baseLayer();
+    XRPresentationContext* prev_output_context = render_state_->outputContext();
+    update_views_next_frame_ = true;
+
+    // Loop through each pending render state and apply it to the active one.
+    for (auto& init : pending_render_state_) {
+      render_state_->Update(init);
+    }
+    pending_render_state_.clear();
+
+    // If this is an inline session and the base layer has changed, give it an
+    // opportunity to update it's drawing buffer size.
+    if (!immersive() && render_state_->baseLayer() &&
+        render_state_->baseLayer() != prev_base_layer) {
+      render_state_->baseLayer()->OnResize();
+    }
+
+    // If the output context changed, remove listeners from the old one and add
+    // listeners to the new one as appropriate.
+    if (render_state_->outputContext() != prev_output_context) {
+      // If we had an output context previously remove anything observing it.
+      DetachOutputContext(prev_output_context);
+
+      // When a new output context is provided, monitor it for resize events.
+      if (render_state_->outputContext()) {
+        render_state_->outputContext()->BindToSession(this);
+        HTMLCanvasElement* canvas = render_state_->outputContext()->canvas();
+        if (canvas) {
+          if (!resize_observer_) {
+            resize_observer_ = ResizeObserver::Create(
+                canvas->GetDocument(),
+                MakeGarbageCollected<XRSessionResizeObserverDelegate>(this));
+          }
+          resize_observer_->observe(canvas);
+
+          // Begin processing input events on the output context's canvas.
+          if (!immersive()) {
+            canvas_input_provider_ =
+                MakeGarbageCollected<XRCanvasInputProvider>(this, canvas);
+          }
+
+          // Get the new canvas dimensions
+          UpdateCanvasDimensions(canvas);
+        }
+      }
+    }
+  }
+}
+
 void XRSession::OnFrame(
     double timestamp,
     std::unique_ptr<TransformationMatrix> base_pose_matrix,
@@ -558,22 +624,7 @@
   base_pose_matrix_ = std::move(base_pose_matrix);
 
   // If there are pending render state changes, apply them now.
-  if (pending_render_state_.size() > 0) {
-    XRLayer* prev_base_layer = render_state_->baseLayer();
-    update_views_next_frame_ = true;
-
-    for (auto& init : pending_render_state_) {
-      render_state_->Update(init);
-    }
-    pending_render_state_.clear();
-
-    // If this is an inline session and the base layer has changed, give it an
-    // opportunity to update it's drawing buffer size.
-    if (!immersive() && render_state_->baseLayer() &&
-        render_state_->baseLayer() != prev_base_layer) {
-      render_state_->baseLayer()->OnResize();
-    }
-  }
+  ApplyPendingRenderState();
 
   if (pending_frame_) {
     pending_frame_ = false;
@@ -584,6 +635,11 @@
     if (!frame_base_layer)
       return;
 
+    // Don't allow frames to be processed if an inline session doesn't have an
+    // output context.
+    if (!immersive() && !render_state_->outputContext())
+      return;
+
     XRFrame* presentation_frame = CreatePresentationFrame();
 
     // Make sure that any frame-bounded changed to the views array take effect.
@@ -931,7 +987,6 @@
 
 void XRSession::Trace(blink::Visitor* visitor) {
   visitor->Trace(xr_);
-  visitor->Trace(output_context_);
   visitor->Trace(render_state_);
   visitor->Trace(pending_render_state_);
   visitor->Trace(views_);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index 0f6bdfd..70eb402 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -66,14 +66,12 @@
   XRSession(XR*,
             device::mojom::blink::XRSessionClientRequest client_request,
             SessionMode mode,
-            XRPresentationContext* output_context,
             EnvironmentBlendMode environment_blend_mode);
   ~XRSession() override = default;
 
   XR* xr() const { return xr_; }
   const String& mode() const { return mode_string_; }
   bool environmentIntegration() const { return environment_integration_; }
-  XRPresentationContext* outputContext() const { return output_context_; }
   const String& environmentBlendMode() const { return blend_mode_string_; }
   XRRenderState* renderState() const { return render_state_; }
 
@@ -124,6 +122,8 @@
   // Reports the size of the output context's, if one is available. If not
   // reports (0, 0);
   DoubleSize OutputCanvasSize() const;
+  XRPresentationContext* outputContext() const;
+  void DetachOutputContext(XRPresentationContext* output_context);
 
   void LogGetPose() const;
 
@@ -179,6 +179,7 @@
 
   XRFrame* CreatePresentationFrame();
   void UpdateCanvasDimensions(Element*);
+  void ApplyPendingRenderState();
 
   void UpdateInputSourceState(
       XRInputSource*,
@@ -203,7 +204,6 @@
   const SessionMode mode_;
   const String mode_string_;
   const bool environment_integration_;
-  const Member<XRPresentationContext> output_context_;
   String blend_mode_string_;
   Member<XRRenderState> render_state_;
   HeapVector<Member<XRRenderStateInit>> pending_render_state_;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl
index 8e5356a..11973209 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -23,7 +23,6 @@
     OriginTrialEnabled=WebXR
 ] interface XRSession : EventTarget {
   readonly attribute XRSessionMode mode;
-  readonly attribute XRPresentationContext outputContext;
   readonly attribute XREnvironmentBlendMode environmentBlendMode;
   readonly attribute XRRenderState renderState;
 
diff --git a/third_party/blink/renderer/modules/xr/xr_session_creation_options.idl b/third_party/blink/renderer/modules/xr/xr_session_creation_options.idl
index 6a876949..fccbf19 100644
--- a/third_party/blink/renderer/modules/xr/xr_session_creation_options.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session_creation_options.idl
@@ -5,5 +5,4 @@
 // https://immersive-web.github.io/webxr/#xrsessioncreationoptions-interface
 dictionary XRSessionCreationOptions {
   XRSessionMode mode = "inline";
-  XRPresentationContext outputContext;
 };
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index b6c3c02f5..fef425f 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -304,7 +304,6 @@
     "animation/compositor_transform_operations.h",
     "animation/timing_function.cc",
     "animation/timing_function.h",
-    "async_file_system_callbacks.h",
     "audio/android/fft_frame_open_maxdl_android.cc",
     "audio/audio_array.h",
     "audio/audio_bus.cc",
@@ -973,6 +972,8 @@
     "graphics/graphics_types.cc",
     "graphics/graphics_types.h",
     "graphics/graphics_types_3d.h",
+    "graphics/high_contrast_color_classifier.cc",
+    "graphics/high_contrast_color_classifier.h",
     "graphics/high_contrast_image_classifier.cc",
     "graphics/high_contrast_image_classifier.h",
     "graphics/high_contrast_settings.h",
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 777649e..69fa1fa 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -189,10 +189,6 @@
   RuntimeEnabledFeatures::SetForceTallerSelectPopupEnabled(enable);
 }
 
-void WebRuntimeFeatures::EnableGamepadVibration(bool enable) {
-  RuntimeEnabledFeatures::SetGamepadVibrationEnabled(enable);
-}
-
 void WebRuntimeFeatures::EnableGenericSensor(bool enable) {
   RuntimeEnabledFeatures::SetSensorEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/fonts/bitmap_glyphs_blacklist.h b/third_party/blink/renderer/platform/fonts/bitmap_glyphs_blacklist.h
index 56ef5d6f..14c27bf 100644
--- a/third_party/blink/renderer/platform/fonts/bitmap_glyphs_blacklist.h
+++ b/third_party/blink/renderer/platform/fonts/bitmap_glyphs_blacklist.h
@@ -6,12 +6,15 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_BITMAP_GLYPHS_BLACKLIST_H_
 
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 class SkTypeface;
 
 namespace blink {
 
 class PLATFORM_EXPORT BitmapGlyphsBlacklist {
+  STATIC_ONLY(BitmapGlyphsBlacklist);
+
  public:
   static bool AvoidEmbeddedBitmapsForTypeface(SkTypeface*);
 };
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_list.h b/third_party/blink/renderer/platform/fonts/font_fallback_list.h
index 519949f7..2d9636e2 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_list.h
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_list.h
@@ -39,6 +39,8 @@
 const int kCAllFamiliesScanned = -1;
 
 class PLATFORM_EXPORT FontFallbackList : public RefCounted<FontFallbackList> {
+  USING_FAST_MALLOC(FontFallbackList);
+
  public:
   static scoped_refptr<FontFallbackList> Create() {
     return base::AdoptRef(new FontFallbackList());
diff --git a/third_party/blink/renderer/platform/fonts/font_global_context.h b/third_party/blink/renderer/platform/fonts/font_global_context.h
index 6419225..eac46454 100644
--- a/third_party/blink/renderer/platform/fonts/font_global_context.h
+++ b/third_party/blink/renderer/platform/fonts/font_global_context.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/text/layout_locale.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 struct hb_font_funcs_t;
 
@@ -22,6 +23,8 @@
 // FontGlobalContext contains non-thread-safe, thread-specific data used for
 // font formatting.
 class PLATFORM_EXPORT FontGlobalContext {
+  USING_FAST_MALLOC(FontGlobalContext);
+
  public:
   static FontGlobalContext* Get(CreateIfNeeded = kCreate);
 
diff --git a/third_party/blink/renderer/platform/fonts/font_selection_types.cc b/third_party/blink/renderer/platform/fonts/font_selection_types.cc
index fc18259..9b624189 100644
--- a/third_party/blink/renderer/platform/fonts/font_selection_types.cc
+++ b/third_party/blink/renderer/platform/fonts/font_selection_types.cc
@@ -30,6 +30,8 @@
 namespace {
 
 class IntegerHasher {
+  STACK_ALLOCATED();
+
  public:
   void add(unsigned integer) {
     m_underlyingHasher.AddCharactersAssumingAligned(integer, integer >> 16);
diff --git a/third_party/blink/renderer/platform/fonts/font_selection_types.h b/third_party/blink/renderer/platform/fonts/font_selection_types.h
index 85d2cb4..7c33e40 100644
--- a/third_party/blink/renderer/platform/fonts/font_selection_types.h
+++ b/third_party/blink/renderer/platform/fonts/font_selection_types.h
@@ -27,6 +27,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_FONT_SELECTION_TYPES_H_
 
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_table_deleted_value_type.h"
 #include "third_party/blink/renderer/platform/wtf/hash_traits.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
@@ -40,6 +41,8 @@
 // means the smallest positive representable value is 0.25, the maximum
 // representable value is 8191.75, and the minimum representable value is -8192.
 class PLATFORM_EXPORT FontSelectionValue {
+  USING_FAST_MALLOC(FontSelectionValue);
+
  public:
   FontSelectionValue() = default;
 
diff --git a/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.h b/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.h
index 58f5390..dab629ed 100644
--- a/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.h
+++ b/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 #if defined(OS_ANDROID) || defined(OS_WIN)
@@ -22,6 +23,8 @@
 class FontTableMatcher;
 
 class FontUniqueNameLookup {
+  USING_FAST_MALLOC(FontUniqueNameLookup);
+
  public:
   // Factory function to construct a platform specific font unique name lookup
   // instance. Client must not use this directly as it is thread
diff --git a/third_party/blink/renderer/platform/fonts/opentype/font_format_check.h b/third_party/blink/renderer/platform/fonts/opentype/font_format_check.h
index bb611db2..cab0acc 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/font_format_check.h
+++ b/third_party/blink/renderer/platform/fonts/opentype/font_format_check.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_OPENTYPE_FONT_FORMAT_CHECK_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_OPENTYPE_FONT_FORMAT_CHECK_H_
 
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "third_party/skia/include/core/SkData.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
@@ -13,6 +14,8 @@
 namespace blink {
 
 class FontFormatCheck {
+  STACK_ALLOCATED();
+
  public:
   FontFormatCheck(sk_sp<SkData>);
   bool IsVariableFont();
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h
index f8786a9d..598638d 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h
@@ -10,12 +10,15 @@
 #include "third_party/blink/renderer/platform/fonts/shaping/case_mapping_harfbuzz_buffer_filler.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
 #include "third_party/blink/renderer/platform/fonts/small_caps_iterator.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 #include <hb.h>
 
 namespace blink {
 
 class PLATFORM_EXPORT OpenTypeCapsSupport {
+  STACK_ALLOCATED();
+
  public:
   OpenTypeCapsSupport();
   OpenTypeCapsSupport(const HarfBuzzFace*,
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h b/third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h
index d6aa2710..4ed64a1 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h
@@ -42,6 +42,8 @@
 
 class PLATFORM_EXPORT OpenTypeVerticalData
     : public RefCounted<OpenTypeVerticalData> {
+  USING_FAST_MALLOC(OpenTypeVerticalData);
+
  public:
   static scoped_refptr<OpenTypeVerticalData> CreateUnscaled(
       sk_sp<SkTypeface> typeface) {
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h
index a25948b3..52cda93b 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h
@@ -47,6 +47,8 @@
 struct BufferSlice;
 
 class PLATFORM_EXPORT HarfBuzzShaper final {
+  DISALLOW_NEW();
+
  public:
   HarfBuzzShaper(const String& text) : text_(text) {}
 
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
index 20f5f6e..fd97fd1 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/text/text_direction.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
@@ -106,6 +107,8 @@
                                         CanvasRotationInVertical);
 
 class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
+  USING_FAST_MALLOC(ShapeResult);
+
  public:
   static scoped_refptr<ShapeResult> Create(const Font* font,
                                     unsigned num_characters,
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h
index 039b216..847c4da 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 #include <hb.h>
 
@@ -32,6 +33,8 @@
 };
 
 class PLATFORM_EXPORT ShapeResultBloberizerTestInfo {
+  STATIC_ONLY(ShapeResultBloberizerTestInfo);
+
  public:
   static const SimpleFontData* PendingRunFontData(
       const ShapeResultBloberizer& bloberizer) {
diff --git a/third_party/blink/renderer/platform/fonts/skia/sktypeface_factory.h b/third_party/blink/renderer/platform/fonts/skia/sktypeface_factory.h
index f13d10e0c..edf7b9f 100644
--- a/third_party/blink/renderer/platform/fonts/skia/sktypeface_factory.h
+++ b/third_party/blink/renderer/platform/fonts/skia/sktypeface_factory.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SKIA_SKTYPEFACE_FACTORY_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SKIA_SKTYPEFACE_FACTORY_H_
 
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/core/SkTypeface.h"
 
@@ -15,6 +16,8 @@
 // benefit of bundling typeface instantiation in one place but needlessly
 // introduces a wrapper for SkTypeface.
 class SkTypeface_Factory {
+  STATIC_ONLY(SkTypeface_Factory);
+
  public:
   static sk_sp<SkTypeface> FromFontConfigInterfaceIdAndTtcIndex(int config_id,
                                                                 int ttc_index);
diff --git a/third_party/blink/renderer/platform/fonts/unicode_range_set.h b/third_party/blink/renderer/platform/fonts/unicode_range_set.h
index b3e729f..f714264 100644
--- a/third_party/blink/renderer/platform/fonts/unicode_range_set.h
+++ b/third_party/blink/renderer/platform/fonts/unicode_range_set.h
@@ -57,6 +57,8 @@
 };
 
 class PLATFORM_EXPORT UnicodeRangeSet : public RefCounted<UnicodeRangeSet> {
+  USING_FAST_MALLOC(UnicodeRangeSet);
+
  public:
   explicit UnicodeRangeSet(const Vector<UnicodeRange>&);
   UnicodeRangeSet() = default;
diff --git a/third_party/blink/renderer/platform/fonts/utf16_ragel_iterator.h b/third_party/blink/renderer/platform/fonts/utf16_ragel_iterator.h
index c2d3e75..e3acb799 100644
--- a/third_party/blink/renderer/platform/fonts/utf16_ragel_iterator.h
+++ b/third_party/blink/renderer/platform/fonts/utf16_ragel_iterator.h
@@ -9,6 +9,7 @@
 
 #include "base/logging.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -20,6 +21,8 @@
 // third-party/emoji-segmenter. The dereferenced character category is cached
 // since Ragel dereferences multiple times without moving the iterator's cursor.
 class PLATFORM_EXPORT UTF16RagelIterator {
+  DISALLOW_NEW();
+
  public:
   UTF16RagelIterator() : buffer_(nullptr), buffer_size_(0), cursor_(0) {}
 
diff --git a/third_party/blink/renderer/platform/fonts/web_font_typeface_factory.h b/third_party/blink/renderer/platform/fonts/web_font_typeface_factory.h
index fcdd7fe3..1f02f7d 100644
--- a/third_party/blink/renderer/platform/fonts/web_font_typeface_factory.h
+++ b/third_party/blink/renderer/platform/fonts/web_font_typeface_factory.h
@@ -8,6 +8,7 @@
 #include "third_party/skia/include/core/SkFontMgr.h"
 
 #include "build/build_config.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #if defined(OS_WIN) || defined(OS_MACOSX)
 #include "third_party/skia/include/ports/SkFontMgr_empty.h"
 #endif
@@ -19,6 +20,8 @@
 // However, for variable fonts, color bitmap font formats and CFF2 fonts we want
 // to use FreeType on Windows and Mac.
 class WebFontTypefaceFactory {
+  STACK_ALLOCATED();
+
  public:
   static bool CreateTypeface(const sk_sp<SkData>, sk_sp<SkTypeface>&);
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index a7df62e..149a4304 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -286,6 +286,10 @@
 
 void PaintArtifactCompositor::UpdateTouchActionRects(
     cc::Layer* layer,
+    // TODO(wangxianzhu): Remove this parameter and use
+    // layer->offset_to_transform_parent() after we fully launch
+    // BlinkGenPropertyTrees.
+    const gfx::Vector2dF& layer_offset,
     const PropertyTreeState& layer_state,
     const PaintChunkSubset& paint_chunks) {
   Vector<HitTestRect> touch_action_rects_in_layer_space;
@@ -303,7 +307,6 @@
         continue;
       }
       LayoutRect layout_rect = LayoutRect(rect.Rect());
-      gfx::Vector2dF layer_offset = layer->offset_to_transform_parent();
       layout_rect.MoveBy(
           LayoutPoint(FloatPoint(-layer_offset.x(), -layer_offset.y())));
       touch_action_rects_in_layer_space.emplace_back(
@@ -849,7 +852,8 @@
     if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
       auto paint_chunks = paint_artifact->GetPaintChunkSubset(
           pending_layer.paint_chunk_indices);
-      UpdateTouchActionRects(layer.get(), property_state, paint_chunks);
+      UpdateTouchActionRects(layer.get(), layer->offset_to_transform_parent(),
+                             property_state, paint_chunks);
     }
 
     layer->SetLayerTreeHost(root_layer_->layer_tree_host());
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index ce54595..9d0f650 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -25,6 +25,7 @@
 }
 
 namespace gfx {
+class Vector2dF;
 class ScrollOffset;
 }
 
@@ -127,6 +128,7 @@
   // Update the cc::Layer's touch action region from the touch action rects of
   // the paint chunks.
   static void UpdateTouchActionRects(cc::Layer*,
+                                     const gfx::Vector2dF& layer_offset,
                                      const PropertyTreeState& layer_state,
                                      const PaintChunkSubset& paint_chunks);
 
diff --git a/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.cc b/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.cc
new file mode 100644
index 0000000..9d1d4c2
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.cc
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.h"
+
+namespace blink {
+
+// Values below which a color is considered sufficiently transparent that a
+// lighter color behind it would make the final color as seen by the user light.
+// TODO(https://crbug.com/925949): This is a placeholder value. It should be
+// replaced with a better researched value before launching high contrast/dark
+// mode.
+const float kOpacityThreshold = 0.4;
+
+// TODO(https://crbug.com/925949): Find a better algorithm for this.
+bool IsLight(const Color& color, float opacity) {
+  // Multiply the opacity by the alpha value to get a more accurate sense of how
+  // transparent the element is.
+  float real_opacity = opacity * (color.Alpha() / 255);
+  // Assume the color is light if the background is sufficiently transparent.
+  if (real_opacity < kOpacityThreshold) {
+    return true;
+  }
+  double hue;
+  double saturation;
+  double lightness;
+  color.GetHSL(hue, saturation, lightness);
+  return lightness > 0.5;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.h b/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.h
new file mode 100644
index 0000000..817712a
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/high_contrast_color_classifier.h
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_HIGH_CONTRAST_COLOR_CLASSIFIER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_HIGH_CONTRAST_COLOR_CLASSIFIER_H_
+
+#include "third_party/blink/renderer/platform/graphics/color.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+
+namespace blink {
+
+bool PLATFORM_EXPORT IsLight(const Color& color, float opacity);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_HIGH_CONTRAST_COLOR_CLASSIFIER_H_
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
index 5d59942..d942a964 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
@@ -109,7 +109,8 @@
                                             TimeTicks,
                                             int64_t,
                                             int64_t,
-                                            bool) {}
+                                            bool,
+                                            ResourceResponseType) {}
 
 void FetchContext::DispatchDidFail(const KURL&,
                                    unsigned long,
@@ -126,8 +127,6 @@
     ResourceType,
     const AtomicString& fetch_initiator_name) {}
 
-void FetchContext::DidLoadResource(Resource*) {}
-
 void FetchContext::DidObserveLoadingBehavior(WebLoadingBehaviorFlag) {}
 
 void FetchContext::AddResourceTiming(const ResourceTimingInfo&) {}
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index a9052896..7776857 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -156,7 +156,8 @@
                                         TimeTicks finish_time,
                                         int64_t encoded_data_length,
                                         int64_t decoded_body_length,
-                                        bool should_report_corb_blocking);
+                                        bool should_report_corb_blocking,
+                                        ResourceResponseType);
   virtual void DispatchDidFail(const KURL&,
                                unsigned long identifier,
                                const ResourceError&,
@@ -171,7 +172,6 @@
                                      ResourceType,
                                      const AtomicString& fetch_initiator_name);
 
-  virtual void DidLoadResource(Resource*);
   virtual void DidObserveLoadingBehavior(WebLoadingBehaviorFlag);
 
   virtual void AddResourceTiming(const ResourceTimingInfo&);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 007bb57e..70fabe4 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -607,7 +607,7 @@
 
   Context().DispatchDidFinishLoading(
       identifier, TimeTicks(), 0, resource->GetResponse().DecodedBodyLength(),
-      false);
+      false, FetchContext::ResourceResponseType::kFromMemoryCache);
 }
 
 Resource* ResourceFetcher::ResourceForStaticData(
@@ -1686,12 +1686,6 @@
   return archive_->MainResource();
 }
 
-void ResourceFetcher::HandleLoadCompletion(Resource* resource) {
-  Context().DidLoadResource(resource);
-
-  resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
-}
-
 void ResourceFetcher::HandleLoaderFinish(
     Resource* resource,
     TimeTicks finish_time,
@@ -1770,10 +1764,6 @@
   }
 
   resource->VirtualTimePauser().UnpauseVirtualTime();
-  Context().DispatchDidFinishLoading(
-      resource->Identifier(), finish_time, encoded_data_length,
-      resource->GetResponse().DecodedBodyLength(), should_report_corb_blocking);
-
   if (type == kDidFinishLoading) {
     resource->Finish(finish_time, task_runner_.get());
 
@@ -1804,8 +1794,11 @@
       context_->DidObserveLoadingBehavior(behavior);
     }
   }
-
-  HandleLoadCompletion(resource);
+  Context().DispatchDidFinishLoading(
+      resource->Identifier(), finish_time, encoded_data_length,
+      resource->GetResponse().DecodedBodyLength(), should_report_corb_blocking,
+      FetchContext::ResourceResponseType::kNotFromMemoryCache);
+  resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
 }
 
 void ResourceFetcher::HandleLoaderError(Resource* resource,
@@ -1824,15 +1817,13 @@
                              fetch_initiator_type_names::kInternal;
 
   resource->VirtualTimePauser().UnpauseVirtualTime();
-  Context().DispatchDidFail(
-      resource->LastResourceRequest().Url(), resource->Identifier(), error,
-      resource->GetResponse().EncodedDataLength(), is_internal_request);
-
   if (error.IsCancellation())
     RemovePreload(resource);
   resource->FinishAsError(error, task_runner_.get());
-
-  HandleLoadCompletion(resource);
+  Context().DispatchDidFail(
+      resource->LastResourceRequest().Url(), resource->Identifier(), error,
+      resource->GetResponse().EncodedDataLength(), is_internal_request);
+  resource->ReloadIfLoFiOrPlaceholderImage(this, Resource::kReloadIfNeeded);
 }
 
 void ResourceFetcher::MoveResourceLoaderToNonBlocking(ResourceLoader* loader) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
index 24ff2a66..3203ef9 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
@@ -321,7 +321,6 @@
                                                 const FetchParameters&);
   void MoveResourceLoaderToNonBlocking(ResourceLoader*);
   void RemoveResourceLoader(ResourceLoader*);
-  void HandleLoadCompletion(Resource*);
 
   void RequestLoadStarted(unsigned long identifier,
                           Resource*,
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 3e2b2bb..b9abc85f 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -579,10 +579,6 @@
       status: "experimental",
     },
     {
-      name: "GamepadVibration",
-      status: "experimental",
-    },
-    {
       name: "GetDisplayMedia",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/text/truncation.h b/third_party/blink/renderer/platform/text/truncation.h
index b221669..cd96a59 100644
--- a/third_party/blink/renderer/platform/text/truncation.h
+++ b/third_party/blink/renderer/platform/text/truncation.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_TRUNCATION_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_TRUNCATION_H_
 
-#include <climits>
+#include <limits>
 
 namespace blink {
 
@@ -17,10 +17,11 @@
 //  representing the characters that are not truncated.
 //
 // Thus the maximum possible length of the text displayed before an ellipsis in
-// a single NGTextFragment or InlineTextBox is |USHRT_MAX - 2| to allow for the
-// no-truncation and full-truncation states.
-const unsigned short kCNoTruncation = USHRT_MAX;
-const unsigned short kCFullTruncation = USHRT_MAX - 1;
+// a single NGTextFragment or InlineTextBox is
+// |std::numeric_limits<uint16_t>::max() - 2| to allow for the no-truncation and
+// full-truncation states.
+constexpr uint16_t kCNoTruncation = std::numeric_limits<uint16_t>::max();
+constexpr uint16_t kCFullTruncation = std::numeric_limits<uint16_t>::max() - 1;
 
 }  // namespace blink
 
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index f5ee624..0ac82aea 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -3,6 +3,8 @@
 Bug(none) http/tests/devtools/tracing/ [ Skip ]
 Bug(none) scrollingcoordinator/ [ Skip ]
 
+Bug(none) virtual/disable-blink-gen-property-trees/ [ Skip ]
+
 Bug(none) virtual/bidi-caret-affinity/ [ Skip ]
 Bug(none) virtual/gpu/fast/canvas/ [ Skip ]
 Bug(none) virtual/layout_ng/ [ Skip ]
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
index 65ce81e..945f7ac 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
@@ -509,6 +509,7 @@
 crbug.com/591099 virtual/bidi-caret-affinity/editing/selection/modify_move/move_right_word_05_rtl_multi_line.html [ Failure ]
 crbug.com/916511 virtual/composite-after-paint/paint/background/scrolling-background-with-negative-z-child.html [ Crash ]
 crbug.com/591099 virtual/composite-after-paint/paint/invalidation/box/margin.html [ Failure Pass ]
+Bug(none) virtual/disable-blink-gen-property-trees/ [ Skip ]
 crbug.com/591099 virtual/display-lock/display-lock/lock-after-append/measure-forced-layout-after-commit.html [ Failure ]
 crbug.com/591099 virtual/display-lock/display-lock/lock-after-append/measure-forced-layout.html [ Failure ]
 crbug.com/591099 virtual/display-lock/display-lock/lock-after-append/measure-updated-layout.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 85ae8a2a..6daf8f5 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -352,16 +352,27 @@
 
 crbug.com/849459 fragmentation/repeating-thead-under-repeating-thead.html [ Failure ]
 
-# As BlinkGenPropertyTrees launches, we do not need to run the non-BlinkGenPropertyTrees tests.
-# TODO(pdr): Remove the stable/{compositing,paint} directories once BlinkGenPropertyTrees has fully launched.
-crbug.com/836886 virtual/stable/compositing [ Skip ]
-crbug.com/836886 virtual/stable/paint [ Skip ]
-
-# These tests are no longer applicable with layer list mode (BGPT).
-# TODO(wangxianzhu): Remove them when we remove non-BGPT code.
+# These tests are no longer applicable in BlinkGenPropertyTree mode.
+# TODO(wangxianzhu): Remove them when we remove non-BlinkGenPropertyTree code.
 Bug(none) http/tests/devtools/layers/layer-sticky-position-constraint-get.js [ Skip ]
 Bug(none) inspector-protocol/layers/get-layers.js [ Skip ]
 
+# Before we fully launch BlinkGenPropertyTrees, run visual/disable-blink-gen-property-trees/ on Linux.
+crbug.com/836884 [ Android ] virtual/disable-blink-gen-property-trees/ [ Skip ]
+crbug.com/836884 [ Win ] virtual/disable-blink-gen-property-trees/ [ Skip ]
+crbug.com/836884 [ Mac ] virtual/disable-blink-gen-property-trees/ [ Skip ]
+crbug.com/836884 [ Fuchsia ] virtual/disable-blink-gen-property-trees/ [ Skip ]
+
+# Failures in non-BlinkGenPropertyTree mode.
+# TODO(wangxianzhu): Remove them when we remove non-BlinkGenPropertyTree code.
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/3d-cube.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/composited-canvas-with-overflowing-object-fit.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/gestures/gesture-tapHighlight-simple-longPress.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/gestures/gesture-tapHighlight-with-filter.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/overflow/clip-escaping-reverse-order-should-not-crash.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/overflow/composited-scroll-with-fractional-translation.html [ Failure ]
+crbug.com/836884 virtual/disable-blink-gen-property-trees/compositing/video/video-controls-layer-creation-squashing.html [ Failure ]
+
 # ==== Regressions introduced by BlinkGenPropertyTrees =====
 # Reflection / mask ordering issue
 crbug.com/767318 compositing/reflections/nested-reflection-mask-change.html [ Failure ]
@@ -850,6 +861,7 @@
 
 crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/balance-table-with-fractional-height-row.html [ Failure ]
 crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/filter-with-abspos.html [ Failure ]
+crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/float-and-block.html [ Failure ]
 crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/float-with-line-after-spanner.html [ Failure ]
 crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/going-out-of-flow-after-spanner.html [ Failure ]
 crbug.com/829028 virtual/layout_ng_experimental/external/wpt/css/css-multicol/inline-block-and-column-span-all.html [ Failure ]
@@ -1100,6 +1112,7 @@
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-content-break.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-edge.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-margin-at-row-boundary.html [ Failure ]
+crbug.com/829028 virtual/layout_ng_experimental/fast/multicol/float-margin-at-row-boundary-fixed-multicol-height.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-moved-by-child-line-and-unbreakable.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-paginate-empty-lines.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/float-paginate.html [ Failure ]
@@ -1202,6 +1215,7 @@
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/single-line.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/span/abspos-containing-block-outside-spanner.html [ Crash Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/span/adjacent-spanners-with-margin.html [ Failure ]
+crbug.com/829028 virtual/layout_ng_experimental/fast/multicol/span/after-float.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/span/after-row-with-uneven-height-nested-multicol.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/span/as-inner-multicol.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/span/autofill-after-spanner.html [ Failure ]
@@ -1275,6 +1289,7 @@
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/table-cell-content-change-with-decorations.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/table-margin-collapse.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/tall-content-in-inner-with-fixed-height.html [ Failure ]
+crbug.com/829028 virtual/layout_ng_experimental/fast/multicol/tall-float1.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/tall-float2.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/three-inner-rows.html [ Failure ]
 crbug.com/874506 virtual/layout_ng_experimental/fast/multicol/unsplittable-inline-block.html [ Failure ]
@@ -4165,6 +4180,7 @@
 # This fails because CORS check?: Error message is like:
 # Access to XMLHttpRequest at 'foobar://abcd' (redirected from 'http://web-platform.test:8001/xhr/resources/redirect.py?location=foobar://abcd&code=301') from origin 'http://web-platform.test:8001' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
 crbug.com/924043 virtual/omt-worker-fetch/external/wpt/xhr/send-redirect-bogus-sync.htm [ Timeout ]
+crbug.com/937048 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Pass Timeout ]
 
 crbug.com/697971 [ Mac10.12 ] fast/text/selection/flexbox-selection-nested.html [ Skip ]
 crbug.com/697971 [ Mac10.12 ] fast/text/selection/flexbox-selection.html [ Skip ]
@@ -5767,7 +5783,6 @@
 crbug.com/922951 virtual/feature-policy-for-sandbox/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Skip ]
 crbug.com/922951 virtual/prefer_compositing_to_lcd_text/scrollbars/resize-scales-with-dpi-150.html [ Skip ]
 crbug.com/922951 virtual/scalefactor150/fast/events/synthetic-events/tap-on-scaled-screen.html [ Skip ]
-crbug.com/922951 virtual/stable/compositing/overflow/overflow-scroll-with-local-background.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode.html [ Skip ]
@@ -5894,12 +5909,12 @@
 # Sheriff 2019-02-25
 crbug.com/935587 [ Mac ] virtual/video-surface-layer/media/stable/video-object-fit-stable.html [ Failure Pass ]
 crbug.com/935638 fast/dom/rtl-scroll-to-leftmost-and-resize.html [ Timeout Pass ]
-crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid.html [ Failure ]
-crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-invalid.html [ Failure ]
-crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-invalid.html [ Failure ]
-crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-invalid.html [ Failure ]
-crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-invalid.html [ Failure ]
-crbug.com/935690 [ Mac ] virtual/video-surface-layer/media/video-played-ranges-1.html [ Failure ]
+crbug.com/935689 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/symbols-function-invalid.html [ Failure Pass ]
+crbug.com/935689 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-additive-invalid.html [ Failure Pass ]
+crbug.com/935689 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-fixed-invalid.html [ Failure Pass ]
+crbug.com/935689 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-numeric-invalid.html [ Failure Pass ]
+crbug.com/935689 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-symbolic-invalid.html [ Failure Pass ]
+crbug.com/935690 virtual/video-surface-layer/media/video-played-ranges-1.html [ Failure ]
 
 # Sheriff 2019-02-26
 crbug.com/935689 [ Mac ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/system-alphabetic-invalid.html [ Failure ]
@@ -5936,3 +5951,6 @@
 crbug.com/915352 virtual/threaded/external/wpt/animation-worklet/scroll-timeline-writing-modes.https.html [ Pass Failure ]
 crbug.com/915352 virtual/threaded/external/wpt/animation-worklet/worklet-animation-pause-resume.https.html [ Pass Failure ]
 crbug.com/915352 virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-fill-mode.https.html [ Pass Failure ]
+
+crbug.com/915352 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Failure ]
+crbug.com/915352 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index eb3ea0c..ba4d91d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -119,14 +119,14 @@
              "--force-raster-color-profile=color-spin-gamma24"]
   },
   {
-    "prefix": "stable",
+    "prefix": "disable-blink-gen-property-trees",
     "base": "compositing",
-    "args": ["--stable-release-mode"]
+    "args": ["--disable-blink-features=BlinkGenPropertyTrees"]
   },
   {
-    "prefix": "stable",
+    "prefix": "disable-blink-gen-property-trees",
     "base": "paint",
-    "args": ["--stable-release-mode"]
+    "args": ["--disable-blink-features=BlinkGenPropertyTrees"]
   },
   {
     "prefix": "stable",
diff --git a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none-expected.html b/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none-expected.html
deleted file mode 100644
index cf25551c..0000000
--- a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none-expected.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html>
-<style>
-#box {
-  width: 100px;
-  height: 100px;
-  background-color: #00ff00;
-  transform: translate(0, 100px);
-  will-change: transform; /* force compositing */
-}
-
-#covered {
-  width: 100px;
-  height: 100px;
-  background-color: #ff8080;
-}
-
-#scroller {
-  overflow: auto;
-  height: 100px;
-  width: 100px;
-  will-change: transform; /* force compositing */
-}
-
-#contents {
-  height: 1000px;
-  width: 100%;
-}
-</style>
-
-<div id="box"></div>
-<div id="covered"></div>
-<div id="scroller">
-  <div id="contents"></div>
-</div>
-
-<script>
-// Move the scroller to halfway.
-const scroller = document.getElementById("scroller");
-const maxScroll = scroller.scrollHeight - scroller.clientHeight;
-scroller.scrollTop = 0.5 * maxScroll;
-</script>
diff --git a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none.html b/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none.html
deleted file mode 100644
index 96a487c..0000000
--- a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-display-none.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE html>
-<style>
-#box {
-  width: 100px;
-  height: 100px;
-  background-color: #00ff00;
-}
-
-#covered {
-  width: 100px;
-  height: 100px;
-  background-color: #ff8080;
-}
-
-#scroller {
-  overflow: auto;
-  height: 100px;
-  width: 100px;
-}
-
-.removed {
-  display: none;
-}
-
-#contents {
-  height: 1000px;
-  width: 100%;
-}
-</style>
-
-<div id="box"></div>
-<div id="covered"></div>
-<div id="scroller">
-  <div id="contents"></div>
-</div>
-
-<script id="visual_update"  type="text/worklet">
-registerAnimator("test_animator", class {
-  animate(currentTime, effect) {
-    effect.localTime = currentTime;
-  }
-});
-</script>
-
-<script src="resources/animation-worklet-tests.js"></script>
-<script>
-if (window.testRunner) {
-  testRunner.waitUntilDone();
-}
-
-runInAnimationWorklet(
-  document.getElementById('visual_update').textContent
-).then(()=>{
-  const box = document.getElementById('box');
-  const effect = new KeyframeEffect(box,
-    [
-     { transform: 'translateY(0)' },
-     { transform: 'translateY(200px)' }
-    ], {
-      duration: 1000,
-    }
-  );
-
-  const scroller = document.getElementById('scroller');
-  scroller.classList.add('removed');
-  const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, orientation: 'block' });
-  const animation = new WorkletAnimation('test_animator', effect, timeline, {});
-  animation.play();
-
-  // Ensure that the WorkletAnimation will have been started on the compositor.
-  waitTwoAnimationFrames(function() {
-    // Now return the scroller to the world, which will cause it to be composited
-    // and the animation should update on the compositor side.
-    scroller.classList.remove('removed');
-    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
-    scroller.scrollTop = 0.5 * maxScroll;
-
-    if (window.testRunner) {
-      waitTwoAnimationFrames(_ => {
-        testRunner.notifyDone();
-      });
-    }
-  });
-});
-</script>
diff --git a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-expected.html b/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-expected.html
deleted file mode 100644
index 44e38599..0000000
--- a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline-expected.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE html>
-<style>
-#box {
-  width: 100px;
-  height: 100px;
-  background-color: #00ff00;
-  transform: translate(0, 100px);
-  opacity: 0.5;
-  will-change: transform; /* force compositing */
-}
-
-#covered {
-  width: 100px;
-  height: 100px;
-  background-color: #ff8080;
-}
-
-#scroller {
-  overflow: auto;
-  height: 100px;
-  width: 100px;
-  will-change: transform; /* force compositing */
-}
-
-#contents {
-  height: 1000px;
-  width: 100%;
-}
-</style>
-
-<div id="box"></div>
-<div id="covered"></div>
-<div id="scroller">
-  <div id="contents"></div>
-</div>
-
-<script>
-window.addEventListener('load', function() {
-  // Move the scroller to halfway.
-  const scroller = document.getElementById("scroller");
-  const maxScroll = scroller.scrollHeight - scroller.clientHeight;
-  scroller.scrollTop = 0.5 * maxScroll;
-});
-</script>
diff --git a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline.html b/third_party/blink/web_tests/animations/animationworklet/scroll-timeline.html
deleted file mode 100644
index 2a63b1b..0000000
--- a/third_party/blink/web_tests/animations/animationworklet/scroll-timeline.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE html>
-<style>
-#box {
-  width: 100px;
-  height: 100px;
-  background-color: #00ff00;
-}
-
-#covered {
-  width: 100px;
-  height: 100px;
-  background-color: #ff8080;
-}
-
-#scroller {
-  overflow: auto;
-  height: 100px;
-  width: 100px;
-}
-
-#contents {
-  height: 1000px;
-  width: 100%;
-}
-</style>
-
-<div id="box"></div>
-<div id="covered"></div>
-<div id="scroller">
-  <div id="contents"></div>
-</div>
-
-<script id="visual_update"  type="text/worklet">
-registerAnimator("test_animator", class {
-  animate(currentTime, effect) {
-    effect.localTime = currentTime;
-  }
-});
-</script>
-
-<script src="resources/animation-worklet-tests.js"></script>
-<script>
-if (window.testRunner) {
-  testRunner.waitUntilDone();
-}
-
-runInAnimationWorklet(
-  document.getElementById('visual_update').textContent
-).then(()=>{
-  const box = document.getElementById('box');
-  const effect = new KeyframeEffect(box,
-    [
-     { transform: 'translateY(0)', opacity: 1},
-     { transform: 'translateY(200px)', opacity: 0}
-    ], {
-      duration: 1000,
-    }
-  );
-
-  const scroller = document.getElementById('scroller');
-  const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, orientation: 'block' });
-  const animation = new WorkletAnimation('test_animator', [effect], timeline, {});
-  animation.play();
-
-  // Move the scroller to the halfway point.
-  const maxScroll = scroller.scrollHeight - scroller.clientHeight;
-  scroller.scrollTop = 0.5 * maxScroll;
-
-  if (window.testRunner) {
-    waitTwoAnimationFrames(_ => {
-      testRunner.notifyDone();
-    });
-  }
-});
-</script>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index d7a8d8a..88e425c 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -6843,6 +6843,30 @@
      {}
     ]
    ],
+   "animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html": [
+    [
+     "/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html",
+     [
+      [
+       "/animation-worklet/worklet-animation-with-scroll-timeline-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "animation-worklet/worklet-animation-with-scroll-timeline.https.html": [
+    [
+     "/animation-worklet/worklet-animation-with-scroll-timeline.https.html",
+     [
+      [
+       "/animation-worklet/worklet-animation-with-scroll-timeline-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "apng/animated-png-timeout.html": [
     [
      "/apng/animated-png-timeout.html",
@@ -116932,6 +116956,11 @@
      {}
     ]
    ],
+   "animation-worklet/worklet-animation-with-scroll-timeline-ref.html": [
+    [
+     {}
+    ]
+   ],
    "apng/META.yml": [
     [
      {}
@@ -160572,6 +160601,11 @@
      {}
     ]
    ],
+   "feature-policy/reporting/unoptimized-images-reporting-onload.html.headers": [
+    [
+     {}
+    ]
+   ],
    "feature-policy/reporting/unoptimized-images-reporting.html.headers": [
     [
      {}
@@ -173902,6 +173936,11 @@
      {}
     ]
    ],
+   "html/user-activation/resources/child-four.html": [
+    [
+     {}
+    ]
+   ],
    "html/user-activation/resources/child-one.html": [
     [
      {}
@@ -236459,6 +236498,12 @@
      {}
     ]
    ],
+   "feature-policy/reporting/unoptimized-images-reporting-onload.html": [
+    [
+     "/feature-policy/reporting/unoptimized-images-reporting-onload.html",
+     {}
+    ]
+   ],
    "feature-policy/reporting/unoptimized-images-reporting.html": [
     [
      "/feature-policy/reporting/unoptimized-images-reporting.html",
@@ -250323,6 +250368,28 @@
      }
     ]
    ],
+   "html/user-activation/activation-transfer-cross-origin-with-click.sub.tentative.html": [
+    [
+     "/html/user-activation/activation-transfer-cross-origin-with-click.sub.tentative.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
+   "html/user-activation/activation-transfer-with-click.tentative.html": [
+    [
+     "/html/user-activation/activation-transfer-with-click.tentative.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
+   "html/user-activation/activation-transfer-without-click.tentative.html": [
+    [
+     "/html/user-activation/activation-transfer-without-click.tentative.html",
+     {}
+    ]
+   ],
    "html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html": [
     [
      "/html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html",
@@ -292369,6 +292436,12 @@
      {}
     ]
    ],
+   "webxr/xrSession_transfer_outputContext.https.html": [
+    [
+     "/webxr/xrSession_transfer_outputContext.https.html",
+     {}
+    ]
+   ],
    "workers/SharedWorker-MessageEvent-source.any.js": [
     [
      "/workers/SharedWorker-MessageEvent-source.any.sharedworker.html",
@@ -310946,6 +311019,18 @@
    "8b72d4e487c455f5e3d4d535a4ddf277fd988d01",
    "testharness"
   ],
+  "animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html": [
+   "6f981854d38877d42b1c7b63afdb9ec989a32d42",
+   "reftest"
+  ],
+  "animation-worklet/worklet-animation-with-scroll-timeline-ref.html": [
+   "fe92232d9afa24f78e9cc7cc3bae341ba2a471bc",
+   "support"
+  ],
+  "animation-worklet/worklet-animation-with-scroll-timeline.https.html": [
+   "000517162af20406e39831afc0b6cefa0b367f2c",
+   "reftest"
+  ],
   "apng/META.yml": [
    "a660c7e19ebdcb13e7457ea0bb5729e18bfe2e0e",
    "support"
@@ -393055,7 +393140,7 @@
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html": [
-   "f94ce03133cedbffdae483b8e1b3d38f3f527fb3",
+   "c88beb38d68432c7e00b611cd70f0f717a7faef9",
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-inset.html": [
@@ -393067,7 +393152,7 @@
    "reftest"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-ref.html": [
-   "a1177fd07208c3435ede5ab783199297c6a22d0c",
+   "2b0fabbda7a40a6f9cf972434818c8c438052cbd",
    "support"
   ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-should-push-ref.html": [
@@ -401122,6 +401207,14 @@
    "599137a55d710fe6b8d3052c05c81915622ea0d0",
    "support"
   ],
+  "feature-policy/reporting/unoptimized-images-reporting-onload.html": [
+   "d39b6807d09767ee2859e800c3ca20b27d70cd12",
+   "testharness"
+  ],
+  "feature-policy/reporting/unoptimized-images-reporting-onload.html.headers": [
+   "10b41235409ea38507d9ffe29a18547174351cc3",
+   "support"
+  ],
   "feature-policy/reporting/unoptimized-images-reporting.html": [
    "fb27a13996a46b0e4592f4d28cc3574ae1745fb5",
    "testharness"
@@ -422158,6 +422251,18 @@
    "96d17e27c892ec988ba5acdfb60fd1f9edd16571",
    "manual"
   ],
+  "html/user-activation/activation-transfer-cross-origin-with-click.sub.tentative.html": [
+   "dca44dde1da873d48c8fc81257ab32166a75ef08",
+   "testharness"
+  ],
+  "html/user-activation/activation-transfer-with-click.tentative.html": [
+   "ebb4f5f5122176a30d3eca1d5ab82dd8e00722de",
+   "testharness"
+  ],
+  "html/user-activation/activation-transfer-without-click.tentative.html": [
+   "50cce1fcc6ac38a745b5748f4e2d3a3fd3982311",
+   "testharness"
+  ],
   "html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html": [
    "63a1da05d0005c33bec1af8a58e7f011d5b09d4c",
    "testharness"
@@ -422166,6 +422271,10 @@
    "8e2b1d07014e8bd754d943e11672fff0719bbb74",
    "testharness"
   ],
+  "html/user-activation/resources/child-four.html": [
+   "d312803411960cc8b079d159ac3659f6de4e0d83",
+   "support"
+  ],
   "html/user-activation/resources/child-one.html": [
    "9c99729b6a98022f7449bae62a7bea616308c0c7",
    "support"
@@ -437775,7 +437884,7 @@
    "manual"
   ],
   "payment-request/payment-request-hasenrolledinstrument-method-protection.https-expected.txt": [
-   "6718c1b5bd3c6678f13d83b0dd795e851826ee11",
+   "20a6f53208ac5e4c29a504f3e444066cce5bba89",
    "support"
   ],
   "payment-request/payment-request-hasenrolledinstrument-method-protection.https.html": [
@@ -437783,7 +437892,7 @@
    "testharness"
   ],
   "payment-request/payment-request-hasenrolledinstrument-method.https-expected.txt": [
-   "92e9c38295af30b46ea8751745cc865f06473c6f",
+   "fe7f16769673e2d5b809417456fd1b0c1546d9cc",
    "support"
   ],
   "payment-request/payment-request-hasenrolledinstrument-method.https.html": [
@@ -467151,7 +467260,7 @@
    "support"
   ],
   "webxr/idlharness.https.window-expected.txt": [
-   "13c4479b1f78178585d58dac74d9027bd88efa67",
+   "df76d0f967220e3d22b38530d9440b3e65dc71c6",
    "support"
   ],
   "webxr/idlharness.https.window.js": [
@@ -467163,7 +467272,7 @@
    "support"
   ],
   "webxr/resources/webxr_util.js": [
-   "26e30c48f36f22d1cb7ef6ffe0e6b9e11979f7b9",
+   "e61e4227715f8184b9d7b6dbdd37be82c8680f78",
    "support"
   ],
   "webxr/webGLCanvasContext_create_xrcompatible.https.html": [
@@ -467191,7 +467300,7 @@
    "testharness"
   ],
   "webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html": [
-   "6cf50f521c4836dede6f763b6e7f3e263e30f68e",
+   "241b5ded0a50495cf21b2b5c78aee8327f384a62",
    "testharness"
   ],
   "webxr/xrDevice_supportsSession_immersive.https.html": [
@@ -467211,19 +467320,19 @@
    "testharness"
   ],
   "webxr/xrSession_cancelAnimationFrame.https.html": [
-   "9b08f93eead693c1c9aa6d7e2458988aa7dae442",
+   "cc7b8802cba01ac03adb53a06308acd5642dd0af",
    "testharness"
   ],
   "webxr/xrSession_cancelAnimationFrame_invalidhandle.https.html": [
-   "bc9b625fb9ed2e9001da1ddaf0a01230631b084e",
+   "e6a9c0665435034fdf493e9ab2d4c8a6731eff13",
    "testharness"
   ],
   "webxr/xrSession_end.https.html": [
-   "2719bdd7dd1b4a73e8b2004f477beecf03d131bb",
+   "6365c7de508a426f674ae239d99846d77af94615",
    "testharness"
   ],
   "webxr/xrSession_identity_referenceSpace.https.html": [
-   "6cb25019fcd4d95f5e312bbf49186d0579a31ddc",
+   "fb18edf6ddd4369887599617a5577de65a126b33",
    "testharness"
   ],
   "webxr/xrSession_mode.https.html": [
@@ -467235,7 +467344,7 @@
    "testharness"
   ],
   "webxr/xrSession_requestAnimationFrame_callback_calls.https.html": [
-   "268efcd596e03125e3f7b90fa7f08eba2ae716a8",
+   "92f7d1f79dcb61e400e21e4f5a80a85e3685ec9f",
    "testharness"
   ],
   "webxr/xrSession_requestAnimationFrame_data_valid.https.html": [
@@ -467243,11 +467352,15 @@
    "testharness"
   ],
   "webxr/xrSession_requestAnimationFrame_getViewerPose.https.html": [
-   "618dc13e4fe6eb6eda2c81eef53e722da3b09190",
+   "8c8e26aa12db1c35e67be8928c4e5f9836a5b5e0",
    "testharness"
   ],
   "webxr/xrSession_requestReferenceSpace.https.html": [
-   "bf4ec677655bf8e8440824cbfd311a2f0c444f7c",
+   "ed31f372ceb2b6c3cbfd34da47cdb801b4f366fc",
+   "testharness"
+  ],
+  "webxr/xrSession_transfer_outputContext.https.html": [
+   "69c52d2bfb4d8d942e233872b40c618accaad1b9",
    "testharness"
   ],
   "workers/META.yml": [
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/createImageBitmap-invalid-args-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/createImageBitmap-invalid-args-expected.txt
deleted file mode 100644
index 9824e58e..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/imagebitmap/createImageBitmap-invalid-args-expected.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-This is a testharness.js-based test.
-PASS createImageBitmap with an HTMLCanvasElement source and sw set to 0
-PASS createImageBitmap with an HTMLCanvasElement source and sh set to 0
-PASS createImageBitmap with an HTMLCanvasElement source and oversized (unallocatable) crop region
-PASS createImageBitmap with an HTMLVideoElement source and sw set to 0
-PASS createImageBitmap with an HTMLVideoElement source and sh set to 0
-PASS createImageBitmap with an HTMLVideoElement source and oversized (unallocatable) crop region
-PASS createImageBitmap with an HTMLVideoElement from a data URL source and sw set to 0
-PASS createImageBitmap with an HTMLVideoElement from a data URL source and sh set to 0
-PASS createImageBitmap with an HTMLVideoElement from a data URL source and oversized (unallocatable) crop region
-PASS createImageBitmap with a bitmap HTMLImageElement source and sw set to 0
-PASS createImageBitmap with a bitmap HTMLImageElement source and sh set to 0
-PASS createImageBitmap with a bitmap HTMLImageElement source and oversized (unallocatable) crop region
-PASS createImageBitmap with a vector HTMLImageElement source and sw set to 0
-PASS createImageBitmap with a vector HTMLImageElement source and sh set to 0
-FAIL createImageBitmap with a vector HTMLImageElement source and oversized (unallocatable) crop region assert_throws: function "() => { throw e }" threw null, not an object
-PASS createImageBitmap with a bitmap SVGImageElement source and sw set to 0
-PASS createImageBitmap with a bitmap SVGImageElement source and sh set to 0
-PASS createImageBitmap with a bitmap SVGImageElement source and oversized (unallocatable) crop region
-PASS createImageBitmap with a vector SVGImageElement source and sw set to 0
-PASS createImageBitmap with a vector SVGImageElement source and sh set to 0
-FAIL createImageBitmap with a vector SVGImageElement source and oversized (unallocatable) crop region assert_throws: function "() => { throw e }" threw null, not an object
-PASS createImageBitmap with an OffscreenCanvas source and sw set to 0
-PASS createImageBitmap with an OffscreenCanvas source and sh set to 0
-PASS createImageBitmap with an OffscreenCanvas source and oversized (unallocatable) crop region
-PASS createImageBitmap with an ImageData source and sw set to 0
-PASS createImageBitmap with an ImageData source and sh set to 0
-PASS createImageBitmap with an ImageData source and oversized (unallocatable) crop region
-PASS createImageBitmap with an ImageBitmap source and sw set to 0
-PASS createImageBitmap with an ImageBitmap source and sh set to 0
-PASS createImageBitmap with an ImageBitmap source and oversized (unallocatable) crop region
-PASS createImageBitmap with a Blob source and sw set to 0
-PASS createImageBitmap with a Blob source and sh set to 0
-PASS createImageBitmap with a Blob source and oversized (unallocatable) crop region
-PASS createImageBitmap with undefined image source.
-PASS createImageBitmap with null image source.
-PASS createImageBitmap with CanvasRenderingContext2D image source.
-PASS createImageBitmap with WebGLRenderingContext image source.
-PASS createImageBitmap with Uint8Array image source.
-PASS createImageBitmap with ArrayBuffer image source.
-PASS createImageBitmap with empty image source.
-PASS createImageBitmap with empty video source.
-PASS createImageBitmap with an oversized canvas source.
-PASS createImageBitmap with an invalid OffscreenCanvas source.
-PASS createImageBitmap with an undecodable blob source.
-PASS createImageBitmap with a broken image source.
-PASS createImageBitmap with an available but undecodable image source.
-PASS createImageBitmap with an available but zero height image source.
-PASS createImageBitmap with an available but zero width image source.
-PASS createImageBitmap with a closed ImageBitmap.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.create2.nonfinite-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.create2.nonfinite-expected.txt
deleted file mode 100644
index b3a4e6d..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.create2.nonfinite-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL createImageData() throws TypeError if arguments are not finite assert_throws: function "function() { ctx.createImageData(Infinity, 10); }" threw object "IndexSizeError: Failed to execute 'createImageData' on 'CanvasRenderingContext2D': The source width is 0." ("IndexSizeError") expected object "TypeError" ("TypeError")
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.get.nonfinite-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.get.nonfinite-expected.txt
deleted file mode 100644
index d6254a0..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.get.nonfinite-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL getImageData() throws TypeError if arguments are not finite assert_throws: function "function() { ctx.getImageData(Infinity, 10, 10, 10); }" did not throw
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.object.ctor.array.bounds-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.object.ctor.array.bounds-expected.txt
deleted file mode 100644
index d5a615d..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.object.ctor.array.bounds-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL ImageData has a usable constructor assert_throws: function "function() { new ImageData(new Uint8ClampedArray(0), 1); }" threw object "IndexSizeError: Failed to construct 'ImageData': The input data has zero elements." that is not a DOMException INVALID_STATE_ERR: property "code" is equal to 1, expected 11
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.put.nonfinite-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.put.nonfinite-expected.txt
deleted file mode 100644
index e48c5ab..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/pixel-manipulation/2d.imageData.put.nonfinite-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL putImageData() throws TypeError if arguments are not finite assert_throws: function "function() { ctx.putImageData(imgdata, Infinity, 10); }" did not throw
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/2dcontext/scroll/2d.scrollPathIntoView.verticalRL-expected.txt b/third_party/blink/web_tests/external/wpt/2dcontext/scroll/2d.scrollPathIntoView.verticalRL-expected.txt
deleted file mode 100644
index d136249..0000000
--- a/third_party/blink/web_tests/external/wpt/2dcontext/scroll/2d.scrollPathIntoView.verticalRL-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL scrollPathIntoView() works in vertical-rl writing mode assert_equals: Math.round(rect.right) === viewportWidth + (canvasWidth - 4 - 16) (got 584[number], expected 865[number]) expected 865 but got 584
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
new file mode 100644
index 0000000..6f98185
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
@@ -0,0 +1,77 @@
+<html class="reftest-wait">
+<title>Scroll timeline with WorkletAnimation and transition from display:none to display:block</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
+<meta name="assert" content="Scroll timeline should properly handle going from display:none to display:block">
+<link rel="match" href="worklet-animation-with-scroll-timeline-ref.html">
+
+<script src="/web-animations/testcommon.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<script src="common.js"></script>
+
+<style>
+  #box {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+  }
+
+  #covered {
+    width: 100px;
+    height: 100px;
+    background-color: red;
+  }
+
+  #scroller {
+    overflow: auto;
+    height: 100px;
+    width: 100px;
+  }
+
+  .removed {
+    display: none;
+  }
+
+  #contents {
+    height: 1000px;
+    width: 100%;
+  }
+</style>
+
+<div id="box"></div>
+<div id="covered"></div>
+<div id="scroller">
+  <div id="contents"></div>
+</div>
+
+<script>
+  registerPassthroughAnimator().then(()=>{
+    const box = document.getElementById('box');
+    const effect = new KeyframeEffect(box,
+      [
+      { transform: 'translateY(0)', opacity: 1 },
+      { transform: 'translateY(200px)', opacity: 0 }
+      ], {
+        duration: 1000,
+      }
+    );
+
+    const scroller = document.getElementById('scroller');
+    scroller.classList.add('removed');
+    const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, orientation: 'block' });
+    const animation = new WorkletAnimation('passthrough', effect, timeline);
+    animation.play();
+
+    // Ensure that the WorkletAnimation will have been started on the compositor.
+    waitForAsyncAnimationFrames(1).then(_ => {
+      // Now return the scroller to the world, which will cause it to be composited
+      // and the animation should update on the compositor side.
+      scroller.classList.remove('removed');
+      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+      scroller.scrollTop = 0.5 * maxScroll;
+
+      waitForAsyncAnimationFrames(1).then(_ => {
+        takeScreenshot();
+      });
+    });
+  });
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
new file mode 100644
index 0000000..fe92232
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Reference for Animation Worklet with scroll timeline tests</title>
+<style>
+  #box {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+    transform: translate(0, 100px);
+    opacity: 0.5;
+    will-change: transform; /* force compositing */
+  }
+
+  #covered {
+    width: 100px;
+    height: 100px;
+    background-color: red;
+  }
+
+  #scroller {
+    overflow: auto;
+    height: 100px;
+    width: 100px;
+    will-change: transform; /* force compositing */
+  }
+
+  #contents {
+    height: 1000px;
+    width: 100%;
+  }
+</style>
+
+<div id="box"></div>
+<div id="covered"></div>
+<div id="scroller">
+  <div id="contents"></div>
+</div>
+
+<script>
+  window.addEventListener('load', function() {
+    // Move the scroller to halfway.
+    const scroller = document.getElementById("scroller");
+    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+    scroller.scrollTop = 0.5 * maxScroll;
+  });
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html
new file mode 100644
index 0000000..0005171
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html
@@ -0,0 +1,67 @@
+<html class="reftest-wait">
+<title>Basic use of scroll timeline with WorkletAnimation</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
+<meta name="assert" content="Should be able to use the scroll timeline to drive the worklet animation timing">
+<link rel="match" href="worklet-animation-with-scroll-timeline-ref.html">
+
+<script src="/web-animations/testcommon.js"></script>
+<script src="/common/reftest-wait.js"></script>
+<script src="common.js"></script>
+
+<style>
+  #box {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+  }
+
+  #covered {
+    width: 100px;
+    height: 100px;
+    background-color: red;
+  }
+
+  #scroller {
+    overflow: auto;
+    height: 100px;
+    width: 100px;
+  }
+
+  #contents {
+    height: 1000px;
+    width: 100%;
+  }
+</style>
+
+<div id="box"></div>
+<div id="covered"></div>
+<div id="scroller">
+  <div id="contents"></div>
+</div>
+
+<script>
+  registerPassthroughAnimator().then(() => {
+    const box = document.getElementById('box');
+    const effect = new KeyframeEffect(box,
+      [
+      { transform: 'translateY(0)', opacity: 1},
+      { transform: 'translateY(200px)', opacity: 0}
+      ], {
+        duration: 1000,
+      }
+    );
+
+    const scroller = document.getElementById('scroller');
+    const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, orientation: 'block' });
+    const animation = new WorkletAnimation('passthrough', effect, timeline);
+    animation.play();
+
+    // Move the scroller to the halfway point.
+    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+    scroller.scrollTop = 0.5 * maxScroll;
+
+    waitForAsyncAnimationFrames(1).then(_ => {
+      takeScreenshot();
+    });
+  });
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html
index f94ce03..c88beb3 100644
--- a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html
@@ -15,7 +15,7 @@
 
   #too-wide {
     display: inline-block;
-    height: 20px;
+    height: 21px;
     width: 250px;
     background: blue;
   }
@@ -24,11 +24,31 @@
     width: 100px;
     height: 100px;
     float: left;
+    /* We use a gradient, which is part of the CSS 'image' type.
+     * We set it up to create a hard diagonal edge from the bottom left to the
+     * top right of #shape, which slices through each pixel along the diagonal.
+     * Theoretically, this should place #too-wide at position 50,50 within
+     * #shape's 100x100 region, but on some devices, the gradient rasterization
+     * may leave pixel 50,49 unshaded enough that #too-wide is placed there
+     * instead. To account for that possible off-by-one rounding scenario,
+     * we set things up as follows:
+     *  - We make #too-wide 1px taller than the corresponding content in the
+     * reference case.
+     *  - We clip the outermost div using a 'clip-path' that only paints
+     * the region where the corresponding content is in the reference case.
+     *  - If the testcase renders properly, then #too-wide will have 1px of
+     * content clipped off of its top or bottom (depending on how the
+     * linear-gradient rasterization and rounding works out). Either way,
+     * it'll match the reference case.
+     */
     shape-outside: linear-gradient(135deg, black, black 50%, transparent 50%);
   }
+  .clip {
+    clip-path: inset(50px 0 30px 0px);
+  }
 </style>
 
-<div style="width: 300px; height: 100px;">
+<div style="width: 300px; height: 100px;" class="clip">
 <div id="shape"></div>
 <span id="too-wide"></span>
 <div>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-ref.html
index a1177fd0..2b0fabb 100644
--- a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-ref.html
@@ -1,6 +1,6 @@
 <!DOCTYPE HTML>
 <meta charset="utf-8">
-<title>Test for retrying floats and pushing them partway down the float area</title>
+<title>Reference for retrying floats and pushing them partway down the float area</title>
 <link rel="author" title="Brad Werth" href="mailto:bwerth@mozilla.com">
 <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
 <style>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-canvas-element/imagedata-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-canvas-element/imagedata-expected.txt
index 35f7a86..7db31ca 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-canvas-element/imagedata-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-canvas-element/imagedata-expected.txt
@@ -2,9 +2,7 @@
 PASS ImageData(w, h), width cannot be 0
 PASS ImageData(w, h), height cannot be 0
 PASS ImageData(w, h), exposed attributes check
-FAIL ImageData(buffer, w), the buffer size must be a multiple of 4 assert_throws: function "function() {
-        new ImageData(new Uint8ClampedArray(3), 1);
-    }" threw object "IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of 4." that is not a DOMException InvalidStateError: property "code" is equal to 1, expected 11
+PASS ImageData(buffer, w), the buffer size must be a multiple of 4
 PASS ImageData(buffer, w), buffer size must be a multiple of the image width
 PASS ImageData(buffer, w, h), buffer.length == 4 * w * h must be true
 FAIL ImageData(buffer, w, opt h), Uint8ClampedArray argument type check assert_throws: function "function() {
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/request-headers.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/request-headers.https.html
new file mode 100644
index 0000000..6ebad4b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/request-headers.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test that cache is being bypassed/validated in no-cache mode on update</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+// Tests a service worker script fetch during an update check which
+// bypasses/validates the browser cache. The fetch should have the
+// 'if-none-match' request header.
+//
+// This tests the Update step:
+//  "Set request’s cache mode to "no-cache" if any of the following are true..."
+// https://w3c.github.io/ServiceWorker/#update-algorithm
+//
+// The test works by registering a service worker with |updateViaCache|
+// set to "none". It then does an update. The test server responds with
+// an updated worker script that remembers the http request headers.
+// The updated worker reports back these headers to the test page.
+promise_test(async (t) => {
+  const script = "resources/test-request-headers-worker.py";
+  const scope = "resources/";
+
+  // Register the service worker.
+  await service_worker_unregister(t, scope);
+  const registration = await navigator.serviceWorker.register(
+      script, {scope, updateViaCache: 'none'});
+  await wait_for_state(t, registration.installing, 'activated');
+
+  // Do an update.
+  await registration.update();
+
+  // Ask the new worker what the request headers were.
+  const newWorker = registration.installing;
+  const sawMessage = new Promise((resolve) => {
+    navigator.serviceWorker.onmessage = (event) => {
+      resolve(event.data);
+    };
+  });
+  newWorker.postMessage('getHeaders');
+  const result = await sawMessage;
+
+  // Test the result.
+  assert_equals(result['service-worker'], 'script');
+  assert_equals(result['if-none-match'], 'etag');
+}, 'headers in no-cache mode');
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.js
new file mode 100644
index 0000000..71aff5e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.js
@@ -0,0 +1,7 @@
+// The server injects the request headers here as a JSON string.
+const headersAsJson = `%HEADERS%`;
+const headers = JSON.parse(headersAsJson);
+
+self.addEventListener('message', async (e) => {
+  e.source.postMessage(headers);
+});
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.py b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.py
new file mode 100644
index 0000000..5666f19e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/test-request-headers-worker.py
@@ -0,0 +1,16 @@
+import json
+import os
+
+def main(request, response):
+  path = os.path.join(os.path.dirname(__file__),
+                      "test-request-headers-worker.js")
+  body = open(path, "rb").read()
+
+  data = {key:request.headers[key] for key,value in request.headers.iteritems()}
+  body = body.replace("%HEADERS%", json.dumps(data))
+
+  headers = []
+  headers.append(("ETag", "etag"))
+  headers.append(("Content-Type", 'text/javascript'))
+
+  return headers, body
diff --git a/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
index 13c4479..df76d0f9 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 215 tests; 198 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 215 tests; 197 PASS, 18 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS Partial interface Navigator: original interface defined
 PASS Partial dictionary WebGLContextAttributes: original dictionary defined
@@ -27,7 +27,7 @@
 PASS XRSession interface: existence and properties of interface prototype object's "constructor" property
 PASS XRSession interface: existence and properties of interface prototype object's @@unscopables property
 PASS XRSession interface: attribute mode
-PASS XRSession interface: attribute outputContext
+FAIL XRSession interface: attribute outputContext assert_true: The prototype object must have a property "outputContext" expected true got false
 PASS XRSession interface: attribute environmentBlendMode
 PASS XRSession interface: attribute renderState
 FAIL XRSession interface: attribute viewerSpace assert_true: The prototype object must have a property "viewerSpace" expected true got false
diff --git a/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_util.js b/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_util.js
index 26e30c4..e61e4227 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_util.js
+++ b/third_party/blink/web_tests/external/wpt/webxr/resources/webxr_util.js
@@ -54,7 +54,9 @@
                               // Session must have a baseLayer or frame requests
                               // will be ignored.
                               session.updateRenderState({
-                                  baseLayer: new XRWebGLLayer(session, gl) });
+                                  baseLayer: new XRWebGLLayer(session, gl),
+                                  outputContext: getOutputContext()
+                              });
                               resolve(func(session, testDeviceController, t));
                             })
                             .catch((err) => {
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html
index 6cf50f52..241b5de 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html
@@ -9,8 +9,7 @@
       (t) => {
         return XRTest.simulateDeviceConnection({ supportsImmersive:false })
           .then( (controller) => navigator.xr.requestSession({
-            mode: 'inline',
-            outputContext: getOutputContext()
+            mode: 'inline'
           }))
           .then( (session) => { assert_not_equals(session, null); });
       });
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame.https.html
index 9b08f93..cc7b880 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame.https.html
@@ -14,7 +14,7 @@
     let fakeDeviceInitParams = { supportsImmersive:true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    let nonImmersiveSessionOptions = { mode: 'inline' };
 
     let testFunction = (session) => new Promise((resolve, reject) => {
 
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame_invalidhandle.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame_invalidhandle.https.html
index bc9b625..e6a9c06 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame_invalidhandle.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_cancelAnimationFrame_invalidhandle.https.html
@@ -13,7 +13,7 @@
     let fakeDeviceInitParams = { supportsImmersive:true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    let nonImmersiveSessionOptions = { mode: 'inline' };
 
     let testFunction = (testSession) => new Promise((resolve) => {
       let counter = 0;
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_end.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_end.https.html
index 2719bdd..6365c7d 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_end.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_end.https.html
@@ -12,7 +12,7 @@
     const fakeDeviceInitParams = { supportsImmersive:true };
 
     const immersiveSessionOptions = { mode: 'immersive-vr' };
-    const nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    const nonImmersiveSessionOptions = { mode: 'inline' };
 
     let testFunction = function(session, testDeviceController, t) {
       let eventWatcher = new EventWatcher(t, session, ["end", "watcherdone"]);
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_identity_referenceSpace.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_identity_referenceSpace.https.html
index 6cb2501..fb18edf 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_identity_referenceSpace.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_identity_referenceSpace.https.html
@@ -15,7 +15,7 @@
     let fakeDeviceInitParams = { supportsImmersive: true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let inlineSessionOptions = { outputContext: getOutputContext() };
+    let inlineSessionOptions = { mode: 'inline' };
 
     const identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
 
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_callback_calls.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_callback_calls.https.html
index 268efcd..92f7d1f7 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_callback_calls.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_callback_calls.https.html
@@ -14,7 +14,7 @@
     let fakeDeviceInitParams = { supportsImmersive:true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    let nonImmersiveSessionOptions = { mode: 'inline' };
 
     let testFunction = (testSession) => new Promise((resolve) => {
       function onFrame(time, xrFrame) {
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_getViewerPose.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_getViewerPose.https.html
index 618dc13..8c8e26a 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_getViewerPose.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestAnimationFrame_getViewerPose.https.html
@@ -15,7 +15,7 @@
     let fakeDeviceInitParams = { supportsImmersive: true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    let nonImmersiveSessionOptions = { mode: 'inline' };
 
     // Valid matrices for  when we don't care about specific values
     const validPoseMatrix = [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1];
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestReferenceSpace.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestReferenceSpace.https.html
index bf4ec67..ed31f37 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestReferenceSpace.https.html
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_requestReferenceSpace.https.html
@@ -14,7 +14,7 @@
     let fakeDeviceInitParams = { supportsImmersive: true };
 
     let immersiveSessionOptions = { mode: 'immersive-vr' };
-    let nonImmersiveSessionOptions = { outputContext: getOutputContext() };
+    let nonImmersiveSessionOptions = { mode: 'inline' };
 
     let testFunction = function(session, fakeDeviceController, t) {
       return promise_rejects(t, new TypeError(), session.requestReferenceSpace({ type: "foo" }))
diff --git a/third_party/blink/web_tests/external/wpt/webxr/xrSession_transfer_outputContext.https.html b/third_party/blink/web_tests/external/wpt/webxr/xrSession_transfer_outputContext.https.html
new file mode 100644
index 0000000..69c52d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webxr/xrSession_transfer_outputContext.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src="resources/webxr_util.js"></script>
+  <script>
+    xr_promise_test(
+      "Ensure that XRPresentationContexts are properly transfered between session",
+      (t) => {
+        return XRTest.simulateDeviceConnection({ supportsImmersive:false })
+          .then( (controller) => {
+            Promise.all([
+              navigator.xr.requestSession({ mode: 'inline'}),
+              navigator.xr.requestSession({ mode: 'inline'})
+            ]).then((sessions) => {
+              assert_not_equals(sessions[0], null);
+              assert_not_equals(sessions[1], null);
+
+              let outputContext = getOutputContext();
+
+              sessions[0].updateRenderState({
+                baseLayer: new XRWebGLLayer(session, gl),
+                outputContext: outputContext
+              });
+
+              sessions[0].requestAnimationFrame((time, xrFrame) => {
+                sessions[1].updateRenderState({
+                  baseLayer: new XRWebGLLayer(session, gl),
+                  outputContext: outputContext
+                });
+
+                // outputContext reassignment should not happen until the next frame is processed.
+                assert_equals(sessions[0].renderState.outputContext, outputContext);
+                assert_equals(sessions[1].renderState.outputContext, null);
+
+                sessions[1].requestAnimationFrame((time, xrFrame) => {
+                  // Ensure the outputContext was properly reassigned from one context to the other.
+                  assert_equals(sessions[0].renderState.outputContext, null);
+                  assert_equals(sessions[1].renderState.outputContext, outputContext);
+                });
+              });
+            });
+          });
+      });
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js
index 8dd406c1..2ae10e0 100644
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js
+++ b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_touch-action-table-test_touch-manual-automation.js
@@ -10,6 +10,6 @@
   }).then(function() {
     return touchScrollInTarget('#cell3', 'right');
   }).then(function() {
-    touchTapInTarget('#btnComplete');
+    return touchTapInTarget('#btnComplete');
   });
 }
diff --git a/third_party/blink/web_tests/fast/canvas-api/canvas-2d-imageData-create-nonfinite.html b/third_party/blink/web_tests/fast/canvas-api/canvas-2d-imageData-create-nonfinite.html
index d720c8e..3311bcf1 100644
--- a/third_party/blink/web_tests/fast/canvas-api/canvas-2d-imageData-create-nonfinite.html
+++ b/third_party/blink/web_tests/fast/canvas-api/canvas-2d-imageData-create-nonfinite.html
@@ -6,25 +6,25 @@
     var canvas = document.getElementById('canvas');
     var ctx = canvas.getContext('2d');
 
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(Infinity, Infinity);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(Infinity, 10);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(-Infinity, 10);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(10, Infinity);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(10, -Infinity);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(NaN, 10);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws(new TypeError(), function() {
       ctx.createImageData(10, NaN);
     });
 }, 'Test the argument bounds of canvas createImageData.');
diff --git a/third_party/blink/web_tests/fast/canvas-api/canvas-ImageData-constructor.html b/third_party/blink/web_tests/fast/canvas-api/canvas-ImageData-constructor.html
index a60d796..5e8e5e2 100644
--- a/third_party/blink/web_tests/fast/canvas-api/canvas-ImageData-constructor.html
+++ b/third_party/blink/web_tests/fast/canvas-api/canvas-ImageData-constructor.html
@@ -55,7 +55,7 @@
     assert_throws("INDEX_SIZE_ERR", function() {
       new ImageData(new Uint8Array(100), 25);
     });
-    assert_throws("INDEX_SIZE_ERR", function() {
+    assert_throws("INVALID_STATE_ERR", function() {
       new ImageData(new Uint8ClampedArray(27), 2);
     });
     assert_throws("INDEX_SIZE_ERR", function() {
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-ImageData-workers-expected.txt b/third_party/blink/web_tests/fast/canvas/canvas-ImageData-workers-expected.txt
index 9a5712e6..78324d78 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-ImageData-workers-expected.txt
+++ b/third_party/blink/web_tests/fast/canvas/canvas-ImageData-workers-expected.txt
@@ -18,7 +18,7 @@
 PASS [Worker] new ImageData(1 << 31, 1 << 31) threw exception IndexSizeError: Failed to construct 'ImageData': The requested image size exceeds the supported range..
 PASS [Worker] new ImageData(new Uint8ClampedArray(0)) threw exception TypeError: Failed to construct 'ImageData': 2 arguments required, but only 1 present..
 PASS [Worker] new ImageData(new Uint8Array(100), 25) threw exception IndexSizeError: Failed to construct 'ImageData': The source width is zero or not a number..
-PASS [Worker] new ImageData(new Uint8ClampedArray(27), 2) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of 4..
+PASS [Worker] new ImageData(new Uint8ClampedArray(27), 2) threw exception InvalidStateError: Failed to construct 'ImageData': The input data length is not a multiple of 4..
 PASS [Worker] new ImageData(new Uint8ClampedArray(28), 7, 0) threw exception IndexSizeError: Failed to construct 'ImageData': The source height is zero or not a number..
 PASS [Worker] new ImageData(new Uint8ClampedArray(104), 14) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not a multiple of (4 * width)..
 PASS [Worker] new ImageData(new Uint8ClampedArray([12, 34, 168, 65328]), 1, 151) threw exception IndexSizeError: Failed to construct 'ImageData': The input data length is not equal to (4 * width * height)..
diff --git a/third_party/blink/web_tests/fast/events/pointerevents/pointerevent_touch-adjustment_click_target.html b/third_party/blink/web_tests/fast/events/pointerevents/pointerevent_touch-adjustment_click_target.html
index 99b3359..7d4952df 100644
--- a/third_party/blink/web_tests/fast/events/pointerevents/pointerevent_touch-adjustment_click_target.html
+++ b/third_party/blink/web_tests/fast/events/pointerevents/pointerevent_touch-adjustment_click_target.html
@@ -3,16 +3,34 @@
 <title>Touch-generated events should have the same target</title>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
-<body onload="inject_input()">
 <p>Touch letter 'O' below to run the test. If a "PASS" result appears the test passes, otherwise it fails</p>
 <p><a href="#" id="link">Link</a> <span id="target">O</span></p>
 <div id="log"></div>
-</body>
 <script>
+let input_gesture;
 const target = document.getElementById('target');
 const xPosition = target.offsetLeft + 2;
 const yPosition = target.offsetTop + 2;
 
+function inject_input() {
+  return new Promise(function(resolve, reject) {
+    if (window.chrome && chrome.gpuBenchmarking) {
+      chrome.gpuBenchmarking.pointerActionSequence( [
+        {source: 'touch',
+         actions: [
+            { name: 'pointerDown', x: xPosition, y: yPosition },
+            { name: 'pointerUp' }
+        ]}], resolve);
+    } else {
+      reject();
+    }
+  });
+}
+
+addEventListener('load', () => {
+  input_gesture = inject_input();
+});
+
 async_test(t => {
     const link = document.getElementById('link');
     const expectedEventLog = ['pointerdown-link', 'touchstart-link', 'pointerup-link', 'touchend-link', 'click-link'];
@@ -41,25 +59,9 @@
             eventLogRecorder.push(`${event.type}-${targetName}`);
             if (event.type === 'click') {
                 assert_array_equals(eventLogRecorder, expectedEventLog);
-                t.done();
+                input_gesture.then(() => { t.done(); });
             }
         }));
     }
 });
 </script>
-<script>
-    function inject_input() {
-      return new Promise(function(resolve, reject) {
-        if (window.chrome && chrome.gpuBenchmarking) {
-          chrome.gpuBenchmarking.pointerActionSequence( [
-            {source: 'touch',
-             actions: [
-                { name: 'pointerDown', x: xPosition, y: yPosition },
-                { name: 'pointerUp' }
-            ]}], resolve);
-        } else {
-          reject();
-        }
-      });
-    }
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html b/third_party/blink/web_tests/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html
index a2b8bc1..08f08b3 100644
--- a/third_party/blink/web_tests/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html
+++ b/third_party/blink/web_tests/fast/hidpi/static/pointerevents/pointerevent_touch-adjustment_click_target.html
@@ -3,16 +3,35 @@
 <title>Touch-generated events should have the same target</title>
 <script src="../../../../resources/testharness.js"></script>
 <script src="../../../../resources/testharnessreport.js"></script>
-<body onload="inject_input()">
 <p>Touch letter 'O' below to run the test. If a "PASS" result appears the test passes, otherwise it fails</p>
 <p><a href="#" id="link" style="margin:3px">Link</a> <span id="target">O</span></p>
 <div id="log"></div>
-</body>
 <script>
+let input_gesture;
 const target = document.getElementById('target');
 const xPosition = target.offsetLeft + 2;
 const yPosition = target.offsetTop + 2;
 
+function inject_input() {
+  return new Promise(function(resolve, reject) {
+    if (window.chrome && chrome.gpuBenchmarking) {
+      chrome.gpuBenchmarking.pointerActionSequence( [
+        {source: 'touch',
+         actions: [
+            { name: 'pointerDown', x: xPosition, y: yPosition },
+            { name: 'pointerUp' }
+        ]}], resolve);
+    } else {
+      reject();
+    }
+  });
+}
+
+addEventListener('load', () => {
+  input_gesture = inject_input();
+});
+
+
 async_test(t => {
     const link = document.getElementById('link');
     const expectedEventLog = ['pointerdown-link', 'touchstart-link', 'pointerup-link', 'touchend-link', 'click-link'];
@@ -41,25 +60,9 @@
             eventLogRecorder.push(`${event.type}-${targetName}`);
             if (event.type === 'click') {
                 assert_array_equals(eventLogRecorder, expectedEventLog);
-                t.done();
+                input_gesture.then(() => { t.done(); });
             }
         }));
     }
 });
 </script>
-<script>
-    function inject_input() {
-      return new Promise(function(resolve, reject) {
-        if (window.chrome && chrome.gpuBenchmarking) {
-          chrome.gpuBenchmarking.pointerActionSequence( [
-            {source: 'touch',
-             actions: [
-                { name: 'pointerDown', x: xPosition, y: yPosition },
-                { name: 'pointerUp' }
-            ]}], resolve);
-        } else {
-          reject();
-        }
-      });
-    }
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear-expected.txt b/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear-expected.txt
new file mode 100644
index 0000000..7a0f539f8
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear-expected.txt
@@ -0,0 +1,20 @@
+Tests quota reporting.
+
+Tree element found: true
+Clear storage view is visible: true
+
+Running: Clear all data
+-- B used out of -- storage quota
+Usage breakdown:
+
+Running: Now with data
+-- KB used out of -- storage quota
+Usage breakdown:
+IndexedDB: --.- KB
+Service Workers: --.- B
+
+Running: Clear all data, again
+service worker is stopping
+-- B used out of -- storage quota
+Usage breakdown:
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota.js b/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear.js
similarity index 72%
rename from third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota.js
rename to third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear.js
index d333a7a..cfa52dc 100644
--- a/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota.js
+++ b/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-and-clear.js
@@ -13,7 +13,7 @@
 
   var updateListener = null;
 
-  async function writeArray() {
+  async function writeToIndexdDB() {
     var array = [];
     for (var i = 0; i < 20000; i++)
       array.push(i % 10);
@@ -51,13 +51,23 @@
           typeUsage = children[j].textContent + typeUsage;
         if (children[j].classList.contains('usage-breakdown-legend-value')) {
           // Clean usage value because it's platform-dependent.
-          var cleanedValue = children[j].textContent.replace(/\d+.\d\sKB/, '--.- KB');
+          var cleanedValue = children[j].textContent.replace(/\d+(.\d+)?\sKB/, '--.- KB')
+                                                    .replace(/\d+(.\d+)?\sB/, '--.- B');
           typeUsage = typeUsage + cleanedValue;
         }
       }
       TestRunner.addResult(typeUsage);
     }
   }
+
+  function isServiceWorkerStopping(registration) {
+    const version = registration.versionsByMode().get(SDK.ServiceWorkerVersion.Modes.Redundant);
+    if (!version)
+      return null;
+    const status = version ? version.runningStatus : null;
+    return status === 'stopping';
+  }
+
   UI.viewManager.showView('resources');
 
   var parent = UI.panels.resources._sidebar._applicationTreeElement;
@@ -69,13 +79,33 @@
   var clearStorageView = UI.panels.resources.visibleView;
   TestRunner.addResult('Clear storage view is visible: ' + (clearStorageView instanceof Resources.ClearStorageView));
 
+  TestRunner.markStep('Clear all data');
+
   clearStorageView._clearButton.click();
   await dumpWhenMatches(clearStorageView, usage => usage === 0);
 
   TestRunner.markStep('Now with data');
 
-  await writeArray();
+  await writeToIndexdDB();
+
+  var scriptURL = 'http://127.0.0.1:8000/devtools/service-workers/resources/service-worker-empty.js';
+  var scope = 'http://127.0.0.1:8000/devtools/service-workers/resources/scope/';
+  await ApplicationTestRunner.registerServiceWorker(scriptURL, scope);
   await dumpWhenMatches(clearStorageView, usage => usage > 20000);
 
+  TestRunner.markStep('Clear all data, again');
+
+  for (const serviceWorkerManager of SDK.targetManager.models(SDK.ServiceWorkerManager)) {
+    serviceWorkerManager.addEventListener(
+    SDK.ServiceWorkerManager.Events.RegistrationUpdated, (event) => {
+      if (isServiceWorkerStopping(event.data))
+        TestRunner.addResult('service worker is stopping');
+      }, this);
+  }
+
+  clearStorageView._clearButton.click();
+
+  await dumpWhenMatches(clearStorageView, usage => usage === 0);
+
   TestRunner.completeTest();
 })();
diff --git a/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt b/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt
deleted file mode 100644
index 61726d2f4..0000000
--- a/third_party/blink/web_tests/http/tests/devtools/application-panel/storage-view-reports-quota-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Tests quota reporting.
-
-Tree element found: true
-Clear storage view is visible: true
--- B used out of -- storage quota
-Usage breakdown:
-
-Running: Now with data
--- KB used out of -- storage quota
-Usage breakdown:
-IndexedDB: --.- KB
-
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/interception-test.js b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/interception-test.js
index 4921107..9190380 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/interception-test.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/interception-test.js
@@ -126,9 +126,13 @@
     });
 
     this._session.protocol.Page.onFrameStoppedLoading(() => {
-      frameStoppedLoading = true;
-      this._log(this._getNextId(), 'Page.frameStoppedLoading');
-      maybeCompleteTest();
+      // We want to see errors that might stop frame loading, so we delay
+      // completion a bit.
+      setTimeout(() => {
+        frameStoppedLoading = true;
+        this._log(this._getNextId(), 'Page.frameStoppedLoading');
+        maybeCompleteTest();
+      }, 0);
     });
 
     this._testRunner.log('Test started');
diff --git a/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-backwards.html b/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-backwards.html
index 21cbb2c..095e947 100644
--- a/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-backwards.html
+++ b/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-backwards.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <title>Test that player will jump backwards 10 seconds if double tapped on the left hand side.</title>
+<script src="../../../resources/gesture-util.js"></script>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
@@ -20,6 +21,8 @@
     assert_not_equals(Math.round(video.currentTime), 25);
   });
 
+  let double_tap_gesture;
+
   video.onseeked = t.step_func(() => {
     const currentTime = Math.round(video.currentTime);
 
@@ -27,12 +30,12 @@
       // Double tap on the left side.
       time = currentTime;
       const coordinates = videoLeftEdgeCoordinates(video);
-      doubleTouchAtCoordinates(coordinates[0], coordinates[1]);
+      double_tap_gesture = doubleTapAt(coordinates[0], coordinates[1]);
     } else if (time > 0) {
       // Check the video went back 10 seconds
       assert_greater_than(time, 0);
       assert_equals(currentTime, time - 10);
-      t.done();
+      double_tap_gesture.then(t.step_func_done());
     }
   });
 
diff --git a/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-forwards.html b/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-forwards.html
index b5ef3df0..d15584b 100644
--- a/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-forwards.html
+++ b/third_party/blink/web_tests/media/controls/modern/doubletap-to-jump-forwards.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <title>Test that player will jump forwards 10 seconds if double tapped.</title>
+<script src="../../../resources/gesture-util.js"></script>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
@@ -10,11 +11,13 @@
   const video = document.querySelector('video');
   let time = 0;
 
+  let double_tap_gesture;
+
   video.addEventListener('playing', t.step_func(() => {
     // Double tap on the right side.
     time = Math.round(video.currentTime);
     const coordinates = videoRightEdgeCoordinates(video);
-    doubleTouchAtCoordinates(coordinates[0], coordinates[1]);
+    double_tap_gesture = doubleTapAt(coordinates[0], coordinates[1]);
   }), { once: true });
 
   video.ontimeupdate = t.step_func(() => {
@@ -22,10 +25,12 @@
     assert_not_equals(Math.round(video.currentTime), 5);
   });
 
-  video.addEventListener('seeked', t.step_func_done(() => {
-    // Check the video advanced 10 seconds
-    assert_equals(Math.round(video.currentTime), time + 10);
-  }), { once: true });
+  video.addEventListener('seeked', () => {
+    double_tap_gesture.then(t.step_func_done(() => {
+          // Check the video advanced 10 seconds
+          assert_equals(Math.round(video.currentTime), time + 10);
+    }));
+  }, { once: true });
 
   video.play();
 });
diff --git a/third_party/blink/web_tests/media/controls/modern/overlay-play-button-tap-to-hide.html b/third_party/blink/web_tests/media/controls/modern/overlay-play-button-tap-to-hide.html
index 729fcc0..96e1e2e 100644
--- a/third_party/blink/web_tests/media/controls/modern/overlay-play-button-tap-to-hide.html
+++ b/third_party/blink/web_tests/media/controls/modern/overlay-play-button-tap-to-hide.html
@@ -18,28 +18,34 @@
   const button = mediaControlsOverlayPlayButtonInternal(video);
   const controls = mediaControls(video);
   const overlay = mediaControlsOverlayPlayButton(video);
+  let call_count = 0;
 
   // Make sure the overlay button is not hidden.
   assert_false(overlay.classList.contains('hidden'));
 
-  // Wait until we have loaded the video and tap on the button.
+  overlay.addEventListener('transitionend', t.step_func(() => {
+    call_count++;
+    if (call_count == 1) {
+      // Now tap anywhere on the controls and make sure the button
+      // unhides itself.
+      assert_true(overlay.classList.contains('hidden'));
+      singleTapOnControl(controls);
+    } else if(call_count == 2) {
+      // The second time a transition ends, it must be the controls becoming
+      // visible.
+      assert_false(overlay.classList.contains('hidden'));
+      t.done();
+    } else {
+      assert_unreached("Unexpected transitionend event");
+    }
+  }));
+
+  // Wait until we have loaded the video and tap on the button. This will
+  // transition the button to visible.
   video.addEventListener('canplay', t.step_func(() => {
     singleTapOnControl(button);
   }), { once: true });
 
-  // When we start playing make sure the overlay button is hidden.
-  video.addEventListener('play', t.step_func(() => {
-    assert_true(overlay.classList.contains('hidden'));
-
-    // Now tap anywhere on the controls and make sure the button
-    // unhides itself.
-    singleTapOnControl(controls, t.step_func(() => {
-      overlay.addEventListener('transitionend', t.step_func_done(() => {
-        assert_false(overlay.classList.contains('hidden'));
-      }));
-    }));
-  }), { once: true });
-
   video.src = '../../content/test.webm';
 });
 </script>
diff --git a/third_party/blink/web_tests/media/controls/modern/singletouch-on-play-button.html b/third_party/blink/web_tests/media/controls/modern/singletouch-on-play-button.html
index 860c963..e14c13a 100644
--- a/third_party/blink/web_tests/media/controls/modern/singletouch-on-play-button.html
+++ b/third_party/blink/web_tests/media/controls/modern/singletouch-on-play-button.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <title>Test that the player pauses if single-touched on the play button.</title>
+<script src="../../../resources/gesture-util.js"></script>
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="../../media-controls.js"></script>
@@ -17,14 +18,18 @@
   video.src='../../content/60_sec_video.webm';
   document.body.appendChild(video);
 
+  let tap_gesture;
+
   video.addEventListener('playing', t.step_func(() => {
     // Single tap in the middle of the button.
     const coordinates =
       elementCoordinates(mediaControlsOverlayPlayButtonInternal(video));
-    singleTouchAtCoordinates(coordinates[0], coordinates[1]);
+    tap_gesture = touchTapOn(coordinates[0], coordinates[1]);
   }), { once: true });
 
-  video.addEventListener('pause', t.step_func_done(), { once: true });
+  video.addEventListener('pause', () => {
+    tap_gesture.then(t.step_func_done());
+  }, { once: true });
 
   video.play();
 });
diff --git a/third_party/blink/web_tests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html b/third_party/blink/web_tests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
index a48b0a5..30887f7 100644
--- a/third_party/blink/web_tests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
+++ b/third_party/blink/web_tests/media/controls/tap-on-overlay-play-button-cant-be-preempted.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <title>Test that a div can't preventDefault on touches to prevent the overlay play button from working.</title>
+<script src="../../resources/gesture-util.js"></script>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script src="../media-controls.js"></script>
@@ -20,11 +21,16 @@
   video.src = '../content/60_sec_video.webm';
   outerDiv.appendChild(video);
 
+  let tap_gesture;
+
   video.addEventListener('loadedmetadata', t.step_func(() => {
-    singleTouchOnControl(mediaControlsOverlayPlayButton(video));
+    const coordinates = elementCoordinates(mediaControlsOverlayPlayButton(video));
+    tap_gesture = touchTapOn(coordinates[0], coordinates[1]);
   }), { once: true });
 
-  video.addEventListener('play', t.done.bind(t), { once: true });
+  video.addEventListener('play', () => {
+    tap_gesture.then(t.done.bind(t));
+  }, { once: true });
 
   ['click', 'touchstart', 'touchmove', 'touchend'].forEach(name => {
     outerDiv.addEventListener(name, evt => {
diff --git a/third_party/blink/web_tests/media/media-controls.js b/third_party/blink/web_tests/media/media-controls.js
index 10ca28b..19646f0 100644
--- a/third_party/blink/web_tests/media/media-controls.js
+++ b/third_party/blink/web_tests/media/media-controls.js
@@ -561,7 +561,6 @@
 }
 
 function singleTapAtCoordinates(xPos, yPos, callback) {
-  let delayCallback = function() { setTimeout(callback); };
   chrome.gpuBenchmarking.pointerActionSequence([
     {
       source: 'mouse',
@@ -570,7 +569,7 @@
         { name: 'pointerUp' }
       ]
     }
-  ], delayCallback);
+  ], callback);
 }
 
 function singleTapOutsideControl(control, callback) {
diff --git a/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt b/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
index 4ab9a280..9f880cef 100644
--- a/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
@@ -24,7 +24,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small test-mode phase-ready state-scrubbing'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small test-mode phase-ready state-stopped'",
       "position": [8, 8],
       "bounds": [320, 240]
     },
diff --git a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
index a2be4ec..e90e866 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
@@ -76,7 +76,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-scrubbing'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-stopped'",
       "position": [8, 8],
       "bounds": [352, 288]
     },
diff --git a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
index fd66c2f..1737809 100644
--- a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
@@ -76,7 +76,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-scrubbing'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-stopped'",
       "position": [8, 8],
       "bounds": [352, 288]
     },
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/README.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/README.txt
new file mode 100644
index 0000000..902eb3c2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/README.txt
@@ -0,0 +1 @@
+# This suite runs tests with --disable-blink-features=BlinkGenPropertyTrees
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-scaled-child-with-border-radius-parent-clip-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-scaled-child-with-border-radius-parent-clip-expected.png
new file mode 100644
index 0000000..446aa85
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-scaled-child-with-border-radius-parent-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-translated-child-with-border-radius-parent-clip-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-translated-child-with-border-radius-parent-clip-expected.png
new file mode 100644
index 0000000..5393c95
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/composited-translated-child-with-border-radius-parent-clip-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/fixed-body-background-positioned-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/fixed-body-background-positioned-expected.txt
new file mode 100644
index 0000000..a9d5368
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/fixed-body-background-positioned-expected.txt
@@ -0,0 +1,68 @@
+{
+  "layers": [
+    {
+      "name": "Root Transform Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Inner Viewport Container Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Overscroll Elasticity Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Page Scale Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Inner Viewport Scroll Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 3700],
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [785, 0],
+      "bounds": [15, 600],
+      "contentsOpaque": true,
+      "drawsContent": false
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -200, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-expected.txt
new file mode 100644
index 0000000..100aca1
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-expected.txt
@@ -0,0 +1,84 @@
+Test CSS clip with composited layers. Left and right sides should look the same.
+
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box'",
+      "bounds": [110, 110],
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [215, 15],
+      "bounds": [110, 110],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited inner'",
+      "bounds": [120, 120],
+      "backgroundColor": "#00000033",
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [15, 15, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [210, 10, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-inside-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-inside-expected.txt
new file mode 100644
index 0000000..3566aba2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-inside-expected.txt
@@ -0,0 +1,85 @@
+Test CSS clip with composited layers. Left and right sides should look the same.
+
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box'",
+      "bounds": [90, 80],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [225, 35],
+      "bounds": [90, 80],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited inner'",
+      "bounds": [120, 120],
+      "backgroundColor": "#00000033",
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [25, 35, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [210, 10, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-with-shadow-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-with-shadow-expected.txt
new file mode 100644
index 0000000..3a44c3e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/clip-with-shadow-expected.txt
@@ -0,0 +1,82 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='composited box'",
+      "bounds": [110, 110],
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [215, 15],
+      "bounds": [110, 110],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited inner'",
+      "bounds": [120, 120],
+      "backgroundColor": "#00000033",
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [15, 15, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [210, 10, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/foreground-layer-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/foreground-layer-expected.txt
new file mode 100644
index 0000000..2b54597
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/foreground-layer-expected.txt
@@ -0,0 +1,125 @@
+ 
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='main box'",
+      "bounds": [340, 340],
+      "backgroundColor": "#FF0000",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='negative child'",
+      "bounds": [50, 50],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='main box' (foreground) Layer",
+      "bounds": [340, 340],
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='main box'",
+      "bounds": [340, 340],
+      "backgroundColor": "#FF0000",
+      "transform": 4
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [70, 70],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='negative child'",
+      "bounds": [50, 50],
+      "drawsContent": false,
+      "transform": 6
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='main box' (foreground) Layer",
+      "position": [70, 70],
+      "bounds": [200, 200],
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 78, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [70, 70, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [352, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 5,
+      "parent": 4,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [70, 70, 0, 1]
+      ]
+    },
+    {
+      "id": 6,
+      "parent": 5,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/layer-due-to-layer-children-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/layer-due-to-layer-children-expected.png
new file mode 100644
index 0000000..59e62676
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/geometry/layer-due-to-layer-children-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/become-composited-nested-iframes-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/become-composited-nested-iframes-expected.txt
new file mode 100644
index 0000000..2021656
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/become-composited-nested-iframes-expected.txt
@@ -0,0 +1,128 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 1500],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [20, 120],
+      "bounds": [284, 204]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [22, 122],
+      "bounds": [280, 200]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [22, 122],
+      "bounds": [280, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [30, 130],
+      "bounds": [252, 172]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [31, 131],
+      "bounds": [250, 170],
+      "backgroundColor": "#C0C0C0"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [31, 131],
+      "bounds": [250, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [20, 344],
+      "bounds": [284, 204]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [22, 346],
+      "bounds": [280, 200]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [22, 346],
+      "bounds": [280, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [30, 354],
+      "bounds": [252, 172]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [31, 355],
+      "bounds": [250, 170],
+      "backgroundColor": "#C0C0C0"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [31, 355],
+      "bounds": [250, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='box' class='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [49, 141, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [49, 365, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/invisible-nested-iframe-show-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/invisible-nested-iframe-show-expected.txt
new file mode 100644
index 0000000..7403391
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/invisible-nested-iframe-show-expected.txt
@@ -0,0 +1,117 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [-22, -22],
+      "bounds": [390, 240]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [23, 23],
+      "bounds": [300, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [23, 23],
+      "bounds": [285, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [23, 23],
+      "bounds": [285, 193]
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [31, 31],
+      "bounds": [252, 172]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [32, 32],
+      "bounds": [250, 170],
+      "backgroundColor": "#C0C0C0"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [32, 32],
+      "bounds": [250, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [23, 23],
+      "bounds": [300, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [308, 23],
+      "bounds": [15, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 3
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 42, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [18, 203, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-iframe-iframe-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-iframe-iframe-expected.txt
new file mode 100644
index 0000000..c3c1d6f8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-iframe-iframe-expected.txt
@@ -0,0 +1,61 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [304, 304]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [2, 2],
+      "bounds": [300, 300],
+      "backgroundColor": "#C0C0C0"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [2, 2],
+      "bounds": [300, 300],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME id='overlap'",
+      "position": [250, 0],
+      "bounds": [304, 304]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [20, 12, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-nested-iframes-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-nested-iframes-expected.txt
new file mode 100644
index 0000000..368814d
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/iframes/overlapped-nested-iframes-expected.txt
@@ -0,0 +1,154 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 1650],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [20, 150],
+      "bounds": [284, 204],
+      "transform": 1
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [22, 152],
+      "bounds": [280, 200],
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [22, 152],
+      "bounds": [280, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [30, 160],
+      "bounds": [252, 172],
+      "transform": 1
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [31, 161],
+      "bounds": [250, 170],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [31, 161],
+      "bounds": [250, 170],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [20, 374],
+      "bounds": [284, 204],
+      "transform": 1
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [22, 376],
+      "bounds": [280, 200],
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [22, 376],
+      "bounds": [280, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [30, 384],
+      "bounds": [252, 172],
+      "transform": 1
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [31, 385],
+      "bounds": [250, 170],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [31, 385],
+      "bounds": [250, 170],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='iframe-content' class='box'",
+      "bounds": [210, 210],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='banner'",
+      "position": [0, 100],
+      "bounds": [785, 120],
+      "backgroundColor": "#00000080",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [49, 171, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [49, 395, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overflow-scroll-overlap-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overflow-scroll-overlap-expected.txt
new file mode 100644
index 0000000..0bc9060
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overflow-scroll-overlap-expected.txt
@@ -0,0 +1,67 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited'",
+      "bounds": [30, 30],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='scroller' class='overflow')",
+      "position": [20, 20],
+      "bounds": [306, 206]
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [23, 23],
+      "bounds": [285, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box'",
+      "position": [43, 29],
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV class='box')",
+      "position": [43, 184],
+      "bounds": [210, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-clipping-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-clipping-expected.txt
new file mode 100644
index 0000000..1d920582
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-clipping-expected.txt
@@ -0,0 +1,129 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 812],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='to-animate1' class='box animating1'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='container'",
+      "position": [58, 230],
+      "bounds": [122, 462],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [59, 231],
+      "bounds": [120, 460],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box gray force-layer'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='to-animate2' class='box animating2'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 6
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box yellow'",
+      "position": [69, 571],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box yellow'",
+      "position": [18, 702],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 5,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 6,
+      "parent": 5,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-expected.txt
new file mode 100644
index 0000000..0f1c74b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-animation-expected.txt
@@ -0,0 +1,69 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='container'",
+      "position": [8, 8],
+      "bounds": [122, 242]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [9, 9],
+      "bounds": [120, 240],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='to-animate' class='animating box'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box'",
+      "position": [19, 129],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [...],
+        [...],
+        [...],
+        [...]
+      ],
+      "origin": [50, 50]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-clipping-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-clipping-expected.txt
new file mode 100644
index 0000000..f2c359c09
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-clipping-expected.txt
@@ -0,0 +1,73 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [50, 50],
+      "bounds": [100, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='child'",
+      "bounds": [500, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='child'",
+      "bounds": [500, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='overlap')",
+      "position": [450, 200],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 200, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transformed-and-clipped-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transformed-and-clipped-expected.txt
new file mode 100644
index 0000000..7f07d31
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transformed-and-clipped-expected.txt
@@ -0,0 +1,61 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container clips'",
+      "bounds": [100, 100],
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='under composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV class='over')",
+      "bounds": [100, 100],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [110, 0, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transforms-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transforms-expected.txt
new file mode 100644
index 0000000..6d2c9d99
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/overlap-transforms-expected.txt
@@ -0,0 +1,61 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='container'",
+      "position": [8, 8],
+      "bounds": [122, 242]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [9, 9],
+      "bounds": [120, 240],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='transformed box'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [19, 19, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/squashing-into-ancestor-clipping-layer-change-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/squashing-into-ancestor-clipping-layer-change-expected.txt
new file mode 100644
index 0000000..623ad00
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-creation/squashing-into-ancestor-clipping-layer-change-expected.txt
@@ -0,0 +1,52 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [100, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='composited'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FA8072",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV id='squashed')",
+      "position": [8, 58],
+      "bounds": [100, 100]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 58, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-tree-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-tree-expected.txt
new file mode 100644
index 0000000..18acd2b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/layer-tree-expected.txt
@@ -0,0 +1,146 @@
+Layer tree:
+{
+  "name": "Root Transform Layer",
+  "drawsContent": false,
+  "children": [
+    {
+      "name": "Inner Viewport Container Layer",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "children": [
+        {
+          "name": "Overscroll Elasticity Layer",
+          "drawsContent": false,
+          "children": [
+            {
+              "name": "Page Scale Layer",
+              "drawsContent": false,
+              "children": [
+                {
+                  "name": "Inner Viewport Scroll Layer",
+                  "bounds": [800, 600],
+                  "drawsContent": false,
+                  "children": [
+                    {
+                      "name": "LayoutView #document",
+                      "bounds": [800, 600],
+                      "drawsContent": false,
+                      "backgroundColor": "#FFFFFF",
+                      "shouldFlattenTransform": false,
+                      "children": [
+                        {
+                          "name": "Scrolling Layer",
+                          "bounds": [780, 580],
+                          "drawsContent": false,
+                          "flattenInheritedTransform": false,
+                          "shouldFlattenTransform": false,
+                          "children": [
+                            {
+                              "name": "Scrolling Contents Layer",
+                              "bounds": [1200, 900],
+                              "contentsOpaque": true,
+                              "backgroundColor": "#FFFFFF",
+                              "flattenInheritedTransform": false
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "name": "Overflow Controls Host Layer",
+          "bounds": [800, 600],
+          "drawsContent": false,
+          "children": [
+            {
+              "name": "Horizontal Scrollbar Layer",
+              "position": [0, 580],
+              "bounds": [780, 20]
+            },
+            {
+              "name": "Vertical Scrollbar Layer",
+              "position": [780, 0],
+              "bounds": [20, 580]
+            },
+            {
+              "name": "Scroll Corner Layer",
+              "position": [780, 580],
+              "bounds": [20, 20]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+
+Layer list
+{
+  "layers": [
+    {
+      "name": "Root Transform Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Inner Viewport Container Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Overscroll Elasticity Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Page Scale Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "Inner Viewport Scroll Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [780, 580],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1200, 900],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [0, 580],
+      "bounds": [780, 20]
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [780, 0],
+      "bounds": [20, 580]
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [780, 580],
+      "bounds": [20, 20]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-expected.png
new file mode 100644
index 0000000..312bcf9
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-with-opacity-expected.png
new file mode 100644
index 0000000..a88d0274c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/lots-of-img-layers-with-opacity-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/direct-image-mask-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/direct-image-mask-expected.png
new file mode 100644
index 0000000..187bc849
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/direct-image-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-added-filters-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-added-filters-expected.png
new file mode 100644
index 0000000..0b86911
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-added-filters-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-removed-filters-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-removed-filters-expected.png
new file mode 100644
index 0000000..bf05d030
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/mask-with-removed-filters-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/masked-ancestor-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/masked-ancestor-expected.png
new file mode 100644
index 0000000..651e08a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/masked-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/multiple-masks-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/multiple-masks-expected.png
new file mode 100644
index 0000000..50633eb
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/multiple-masks-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/simple-composited-mask-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/simple-composited-mask-expected.png
new file mode 100644
index 0000000..a4ea481
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/masks/simple-composited-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/opacity-with-mask-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/opacity-with-mask-expected.png
new file mode 100644
index 0000000..b39d0c8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/opacity-with-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-above-composited-subframe-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-above-composited-subframe-expected.png
new file mode 100644
index 0000000..6fc1bc4
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-above-composited-subframe-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-composited-subframe-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-composited-subframe-expected.png
new file mode 100644
index 0000000..467f6527
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-composited-subframe-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png
new file mode 100644
index 0000000..f4b4fff8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-grandparent-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png
new file mode 100644
index 0000000..f380d7a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-parent-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png
new file mode 100644
index 0000000..61743f1
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-on-two-ancestors-composited-grandchild-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-styles-with-composited-child-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-styles-with-composited-child-expected.png
new file mode 100644
index 0000000..0a96ecf
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/border-radius-styles-with-composited-child-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clear-scroll-parent-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clear-scroll-parent-expected.txt
new file mode 100644
index 0000000..9b7a2ba
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clear-scroll-parent-expected.txt
@@ -0,0 +1,93 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='container'",
+      "position": [8, 8],
+      "bounds": [308, 208],
+      "backgroundColor": "#00FF0080"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [12, 12],
+      "bounds": [285, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [12, 12],
+      "bounds": [285, 530],
+      "backgroundColor": "#00FF0080"
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [8, 8],
+      "bounds": [308, 208],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [297, 12],
+      "bounds": [15, 185],
+      "drawsContent": false
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [297, 197],
+      "bounds": [15, 15]
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='fixed'",
+      "position": [50, 200],
+      "bounds": [200, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [12, 12],
+      "bounds": [80, 80],
+      "drawsContent": false,
+      "hasScrollParent": true
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box red'",
+      "position": [22, 22],
+      "bounds": [100, 100],
+      "backgroundColor": "#FF000080"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false,
+      "hasScrollParent": true
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='box'",
+      "position": [22, 102],
+      "bounds": [100, 100],
+      "backgroundColor": "#0000FF80"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV class='box')",
+      "position": [22, 212],
+      "bounds": [100, 320]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clip-descendents-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clip-descendents-expected.txt
new file mode 100644
index 0000000..048aec8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/clip-descendents-expected.txt
@@ -0,0 +1,166 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [48, 38],
+      "bounds": [60, 70],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='box'",
+      "bounds": [100, 150],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [240, 38],
+      "bounds": [60, 70],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='box'",
+      "bounds": [100, 150],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "position": [48, 230],
+      "bounds": [60, 70]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [48, 230],
+      "bounds": [60, 70],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='box'",
+      "bounds": [100, 150],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 6
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "position": [240, 230],
+      "bounds": [60, 70]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [240, 230],
+      "bounds": [60, 70],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='box'",
+      "bounds": [100, 150],
+      "contentsOpaque": true,
+      "backgroundColor": "#808080",
+      "transform": 8
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [242, 50, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 5,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 242, 0, 1]
+      ]
+    },
+    {
+      "id": 6,
+      "parent": 5,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    },
+    {
+      "id": 7,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [242, 242, 0, 1]
+      ]
+    },
+    {
+      "id": 8,
+      "parent": 7,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 1, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-gains-scrollbars-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-gains-scrollbars-expected.txt
new file mode 100644
index 0000000..285f901d
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-gains-scrollbars-expected.txt
@@ -0,0 +1,210 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 1
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [85, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [85, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='vertical' class='content tall'",
+      "bounds": [10, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [85, 0],
+      "bounds": [15, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 2
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [100, 85],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [200, 85],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='horizontal' class='content wide'",
+      "bounds": [200, 10],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [0, 85],
+      "bounds": [100, 15],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 3
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [85, 85],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='both' class='content wide tall'",
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [0, 85],
+      "bounds": [85, 15],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [85, 0],
+      "bounds": [15, 85],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [85, 85],
+      "bounds": [15, 15],
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='corner' class='container resizeWidget'",
+      "bounds": [100, 100],
+      "transform": 4
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [85, 85],
+      "bounds": [15, 15],
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-loses-scrollbars-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-loses-scrollbars-expected.txt
new file mode 100644
index 0000000..28e47b8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/content-loses-scrollbars-expected.txt
@@ -0,0 +1,132 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='vertical' class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='horizontal' class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='both' class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='corner' class='container'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..b219e48
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-composited-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..b219e48
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/grandchild-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-border-radius-clipping-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-border-radius-clipping-expected.png
new file mode 100644
index 0000000..6a16719
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-border-radius-clipping-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
new file mode 100644
index 0000000..1600edb
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/no-excessive-clip-parent-if-parent-escaped-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/no-excessive-clip-parent-if-parent-escaped-expected.txt
new file mode 100644
index 0000000..cc0d374d
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/no-excessive-clip-parent-if-parent-escaped-expected.txt
@@ -0,0 +1,43 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [8, 8],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [8, 8],
+      "bounds": [100, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [8, 8],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "hasClipParent": true
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/overflow-scrollbar-layers-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/overflow-scrollbar-layers-expected.txt
new file mode 100644
index 0000000..f679344
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/overflow-scrollbar-layers-expected.txt
@@ -0,0 +1,210 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 1
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [85, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [85, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content tall'",
+      "bounds": [10, 200],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [85, 0],
+      "bounds": [15, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 2
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [100, 85],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [200, 85],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content wide'",
+      "bounds": [200, 10],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [0, 85],
+      "bounds": [100, 15],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [100, 100],
+      "transform": 3
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [85, 85],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content wide tall'",
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [0, 85],
+      "bounds": [85, 15],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [85, 0],
+      "bounds": [15, 85],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [85, 85],
+      "bounds": [15, 15],
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container resizeWidget'",
+      "bounds": [100, 100],
+      "transform": 4
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='content'",
+      "bounds": [10, 10],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 4
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [85, 85],
+      "bounds": [15, 15],
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 13, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
new file mode 100644
index 0000000..c8f192a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/reparented-scrollbars-non-sc-anc-expected.txt
@@ -0,0 +1,85 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1208, 821],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [1200, 800],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='positioned'",
+      "position": [8, 8],
+      "bounds": [1200, 800]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [8, 8],
+      "bounds": [1200, 800],
+      "drawsContent": false
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [1200, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='scroller'",
+      "position": [8, 8],
+      "bounds": [1200, 1000]
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [8, 8],
+      "bounds": [1200, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [8, 8],
+      "bounds": [1200, 10000]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='foreground'",
+      "position": [8, 10008],
+      "contentsOpaque": true,
+      "drawsContent": false
+    },
+    {
+      "name": "Overflow Controls Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [1200, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [8, 8],
+      "bounds": [1200, 1000],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [1201, 8],
+      "bounds": [7, 1000],
+      "drawsContent": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt
new file mode 100644
index 0000000..9801d91
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-clip-expected.txt
@@ -0,0 +1,64 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
new file mode 100644
index 0000000..a390599f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-effect-interleave-expected.txt
@@ -0,0 +1,94 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "opacity": 0.899999976158142,
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "position": [20, 0],
+      "bounds": [400, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#00FFFF",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "position": [20, 20],
+      "bounds": [100, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF00FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt
new file mode 100644
index 0000000..fbb4681
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-expected.txt
@@ -0,0 +1,64 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [240, 240],
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [20, 20],
+      "bounds": [200, 200],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "position": [20, 20],
+      "bounds": [400, 400],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [120, 120]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
new file mode 100644
index 0000000..0edab25
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
new file mode 100644
index 0000000..f97eb4f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/rotate-then-clip-z-order-interleave-expected.txt
@@ -0,0 +1,82 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [300, 100],
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [200, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [200, 22],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 2
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV",
+      "bounds": [50, 200],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 100, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [0.707106781186548, 0.707106781186548, 0, 0],
+        [-0.707106781186548, 0.707106781186548, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [150, 50]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
new file mode 100644
index 0000000..15c58a53
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
@@ -0,0 +1,82 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='intervening'",
+      "position": [98, 90],
+      "bounds": [300, 300],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#FFEFD5"
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='scroller'",
+      "position": [98, 90],
+      "bounds": [102, 102]
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [99, 91],
+      "bounds": [100, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [99, 91],
+      "bounds": [100, 180],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='fixed'",
+      "position": [60, 60],
+      "bounds": [80, 80],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "Squashing Containment Layer",
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='scrolled'",
+      "position": [103, 95],
+      "bounds": [60, 40],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF"
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (relative positioned) DIV class='scrolled')",
+      "position": [103, 139],
+      "bounds": [60, 128]
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [98, 90],
+      "bounds": [102, 102],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [192, 91],
+      "bounds": [7, 100],
+      "drawsContent": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-expected.txt
new file mode 100644
index 0000000..224eee29
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-expected.txt
@@ -0,0 +1,217 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer A'",
+      "bounds": [352, 294]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='scroller'",
+      "position": [32, 32],
+      "bounds": [290, 230],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [67, 67],
+      "bounds": [220, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [67, 67],
+      "bounds": [220, 236],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [62, 62],
+      "bounds": [230, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [280, 67],
+      "bounds": [7, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer B'",
+      "position": [349, 0],
+      "bounds": [352, 294]
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='scroller'",
+      "position": [381, 32],
+      "bounds": [290, 230],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [416, 67],
+      "bounds": [220, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [416, 67],
+      "bounds": [220, 236],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='content'",
+      "position": [428, 79],
+      "bounds": [196, 212],
+      "contentsOpaque": true,
+      "backgroundColor": "#DDDDDD"
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [411, 62],
+      "bounds": [230, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [629, 67],
+      "bounds": [7, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer C'",
+      "position": [0, 291],
+      "bounds": [352, 294]
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [46, 337],
+      "bounds": [260, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='scroller'",
+      "position": [31, 322],
+      "bounds": [290, 230],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [66, 357],
+      "bounds": [220, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [66, 357],
+      "bounds": [220, 236],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='content'",
+      "position": [78, 369],
+      "bounds": [196, 212],
+      "contentsOpaque": true,
+      "backgroundColor": "#DDDDDD"
+    },
+    {
+      "name": "Overflow Controls Ancestor Clipping Layer",
+      "position": [46, 337],
+      "bounds": [260, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [61, 352],
+      "bounds": [230, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [279, 357],
+      "bounds": [7, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer D'",
+      "position": [349, 291],
+      "bounds": [352, 294]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='clipper'",
+      "position": [379, 321],
+      "bounds": [292, 200]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [389, 331],
+      "bounds": [272, 180],
+      "drawsContent": false
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [395, 337],
+      "bounds": [260, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='scroller'",
+      "position": [380, 322],
+      "bounds": [290, 230],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [415, 357],
+      "bounds": [220, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [415, 357],
+      "bounds": [220, 236],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='content'",
+      "position": [427, 369],
+      "bounds": [196, 212],
+      "contentsOpaque": true,
+      "backgroundColor": "#DDDDDD"
+    },
+    {
+      "name": "Overflow Controls Ancestor Clipping Layer",
+      "position": [395, 337],
+      "bounds": [260, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [410, 352],
+      "bounds": [230, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [628, 357],
+      "bounds": [7, 160],
+      "drawsContent": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.png
new file mode 100644
index 0000000..57f073e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.txt
new file mode 100644
index 0000000..54bb628
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/scrollbar-layer-placement-negative-z-index-child-expected.txt
@@ -0,0 +1,67 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer'",
+      "bounds": [352, 294]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='content'",
+      "position": [79, 79],
+      "bounds": [196, 212],
+      "contentsOpaque": true,
+      "backgroundColor": "#DDDDDD"
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [62, 62],
+      "bounds": [230, 170],
+      "drawsContent": false
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [280, 67],
+      "bounds": [7, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='outer' (foreground) Layer",
+      "bounds": [352, 294]
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='scroller'",
+      "position": [32, 32],
+      "bounds": [290, 230],
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [67, 67],
+      "bounds": [220, 160],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [67, 67],
+      "bounds": [220, 236],
+      "backgroundColor": "#FFFFFF"
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..b219e48
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-one-clipped-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-one-clipped-expected.png
new file mode 100644
index 0000000..ef41de3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-composited-with-border-radius-ancestor-one-clipped-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-with-border-radius-ancestor-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-with-border-radius-ancestor-expected.png
new file mode 100644
index 0000000..fa893ed
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/siblings-with-border-radius-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/tiled-mask-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/tiled-mask-expected.png
new file mode 100644
index 0000000..49e4d2e8
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/overflow/tiled-mask-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perpendicular-layer-sorting-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perpendicular-layer-sorting-expected.png
new file mode 100644
index 0000000..fa252db
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perpendicular-layer-sorting-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perspective-interest-rect-expected.png b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perspective-interest-rect-expected.png
new file mode 100644
index 0000000..6beffe78
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/perspective-interest-rect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-absolute-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-absolute-expected.txt
new file mode 100644
index 0000000..11a5e14
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-absolute-expected.txt
@@ -0,0 +1,54 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [400, 400]
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 400],
+      "backgroundColor": "#FF0000"
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [400, 400],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='positioned layer'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-fixed-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-fixed-expected.txt
new file mode 100644
index 0000000..11a5e14
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-fixed-expected.txt
@@ -0,0 +1,54 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [400, 400]
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 400],
+      "backgroundColor": "#FF0000"
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [400, 400],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='positioned layer'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [50, 50, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-relative-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-relative-expected.txt
new file mode 100644
index 0000000..7d4fc5b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/rtl/rtl-iframe-relative-expected.txt
@@ -0,0 +1,53 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame (positioned) IFRAME",
+      "bounds": [400, 400]
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [400, 400]
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [400, 400],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='layer'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [242, 58, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/clipping-ancestor-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/clipping-ancestor-expected.txt
new file mode 100644
index 0000000..7298b1c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/clipping-ancestor-expected.txt
@@ -0,0 +1,54 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [200, 10],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='inner'",
+      "bounds": [200, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#F5F5F5",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='hoverable'",
+      "position": [8, 0],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#90EE90"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
new file mode 100644
index 0000000..4a56b34
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-another-clip-layer-expected.txt
@@ -0,0 +1,59 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV",
+      "bounds": [784, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [784, 10],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='inner'",
+      "bounds": [784, 10],
+      "contentsOpaque": true,
+      "backgroundColor": "#F5F5F5",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='hoverable'",
+      "position": [8, 0],
+      "bounds": [216, 100],
+      "backgroundColor": "#90EE90"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt
new file mode 100644
index 0000000..f13b579
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/squashing/no-squashing-into-fixed-position-that-clips-expected.txt
@@ -0,0 +1,59 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='fixedpos'",
+      "position": [0, 50],
+      "bounds": [800, 550],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6"
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [0, 50],
+      "bounds": [800, 550],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='compositedlayer'",
+      "bounds": [24, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='notsquashedelement'",
+      "bounds": [800, 60],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000"
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [400, 40, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/visibility/layer-visible-content-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/visibility/layer-visible-content-expected.txt
new file mode 100644
index 0000000..0b05974b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/compositing/visibility/layer-visible-content-expected.txt
@@ -0,0 +1,41 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='container'",
+      "bounds": [200, 200]
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [200, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited box'",
+      "bounds": [10, 10],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow PRE id='layer-tree'",
+      "bounds": [800, 16],
+      "opacity": 0
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/README.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/README.txt
new file mode 100644
index 0000000..902eb3c2
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/README.txt
@@ -0,0 +1 @@
+# This suite runs tests with --disable-blink-features=BlinkGenPropertyTrees
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/clip/clip-path-constant-repaint-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/clip/clip-path-constant-repaint-expected.txt
new file mode 100644
index 0000000..6409953
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/clip/clip-path-constant-repaint-expected.txt
@@ -0,0 +1,63 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='clip'",
+      "bounds": [800, 300],
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#FF0000E6",
+      "maskLayer": [
+        {
+          "name": "Mask Layer",
+          "bounds": [800, 300],
+          "paintInvalidations": [
+            {
+              "object": "Mask Layer",
+              "rect": [0, 0, 800, 300],
+              "reason": "paint property change"
+            }
+          ]
+        }
+      ],
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 100, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/clipping-should-not-repaint-composited-descendants-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/clipping-should-not-repaint-composited-descendants-expected.txt
new file mode 100644
index 0000000..690d34d
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/clipping-should-not-repaint-composited-descendants-expected.txt
@@ -0,0 +1,100 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 616],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='clipping-container'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='clipped-composited-child'",
+      "bounds": [252, 252],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV class='clipping-container with-initial-clipping'",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [100, 100],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='clipped-composited-child'",
+      "bounds": [252, 252],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFF00",
+      "transform": 4
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 108, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [-100, -100, 0, 1]
+      ]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [108, 408, 0, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [-100, -100, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/overlap-test-with-filter-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/overlap-test-with-filter-expected.txt
new file mode 100644
index 0000000..06f94a7
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/overlap-test-with-filter-expected.txt
@@ -0,0 +1,69 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "bounds": [300, 100]
+    },
+    {
+      "name": "LayoutView #document",
+      "bounds": [300, 100],
+      "backgroundColor": "#FFFF00"
+    },
+    {
+      "name": "Child Containment Layer",
+      "bounds": [300, 100],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow BODY",
+      "bounds": [284, 84],
+      "backgroundColor": "#FFFF00",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV",
+      "bounds": [300, 100],
+      "contentsOpaque": true,
+      "backgroundColor": "#D3D3D3",
+      "transform": 2
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [151, 0, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt
new file mode 100644
index 0000000..15fb475
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt
@@ -0,0 +1,59 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [185, 185],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV id='foo2'",
+      "bounds": [150, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#ADD8E6",
+      "transform": 1
+    },
+    {
+      "name": "Squashing Layer (first squashed layer: LayoutBlockFlow (positioned) DIV id='foo')",
+      "position": [8, 8],
+      "bounds": [100, 1000],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='foo'",
+          "rect": [0, 0, 100, 1000],
+          "reason": "background"
+        }
+      ]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
new file mode 100644
index 0000000..e24b05c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
@@ -0,0 +1,84 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow HTML",
+      "bounds": [800, 318]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='neg-z'",
+      "position": [9, -81],
+      "bounds": [100, 410]
+    },
+    {
+      "name": "LayoutBlockFlow HTML (foreground) Layer",
+      "bounds": [800, 318]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container'",
+      "position": [8, 8],
+      "bounds": [102, 302],
+      "backfaceVisibility": "hidden"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [9, 9],
+      "bounds": [100, 300],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [9, 9],
+      "bounds": [100, 430],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [8, 8],
+      "bounds": [102, 302],
+      "drawsContent": false,
+      "backfaceVisibility": "hidden"
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [9, 309],
+      "bounds": [100, 0]
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [109, 9],
+      "bounds": [0, 300]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -100, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
new file mode 100644
index 0000000..b479ab4
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-expected.txt
@@ -0,0 +1,56 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='composited-box'",
+      "position": [38, 38],
+      "bounds": [20, 70],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='composited-box'",
+          "rect": [0, 0, 20, 70],
+          "reason": "full layer"
+        },
+        {
+          "object": "LayoutBlockFlow (positioned) DIV id='composited-box'",
+          "rect": [0, 0, 10, 60],
+          "reason": "full layer"
+        }
+      ]
+    },
+    {
+      "name": "Child Containment Layer",
+      "position": [38, 38],
+      "bounds": [90, 90],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited child'",
+      "position": [8, 58],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000"
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-on-overflow-change-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-on-overflow-change-expected.txt
new file mode 100644
index 0000000..06aafe1
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/should-not-repaint-composited-descendants-on-overflow-change-expected.txt
@@ -0,0 +1,65 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited-child'",
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [0, 200],
+      "bounds": [200, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow DIV class='composited-child'",
+      "position": [0, 200],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited-child overflow-child'",
+      "position": [150, 150],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [0, 200],
+      "bounds": [200, 200],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV class='composited-child overflow-child'",
+      "position": [150, 350],
+      "bounds": [100, 100],
+      "contentsOpaque": true,
+      "backfaceVisibility": "hidden",
+      "backgroundColor": "#008000"
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/subpixel-offset-scaled-transform-composited-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/subpixel-offset-scaled-transform-composited-expected.txt
new file mode 100644
index 0000000..b89515a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/compositing/subpixel-offset-scaled-transform-composited-expected.txt
@@ -0,0 +1,193 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container1' class='container scale'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='child1' class='child composited'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 2
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='child2' class='child scale composited'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 4
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container3' class='container scale composited'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "transform": 6
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container4' class='container composited'",
+      "position": [9, 158],
+      "bounds": [40, 40],
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='container4' class='container composited'",
+          "rect": [0, 0, 40, 40],
+          "reason": "full layer"
+        },
+        {
+          "object": "LayoutBlockFlow (relative positioned) DIV id='container4' class='container composited'",
+          "rect": [0, 0, 40, 40],
+          "reason": "full layer"
+        }
+      ]
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container5' class='container scale composited'",
+      "contentsOpaque": true,
+      "drawsContent": false,
+      "transform": 8
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='child5' class='child composited'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 8
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='container6' class='container composited'",
+      "position": [8, 258],
+      "contentsOpaque": true,
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (relative positioned) DIV id='child6' class='child scale composited'",
+      "bounds": [1, 1],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
+      "transform": 10
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [40, 0, 0, 0],
+        [0, 40, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    },
+    {
+      "id": 3,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [9, 58, 0, 1]
+      ]
+    },
+    {
+      "id": 4,
+      "parent": 3,
+      "transform": [
+        [40, 0, 0, 0],
+        [0, 40, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    },
+    {
+      "id": 5,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 108, 0, 1]
+      ]
+    },
+    {
+      "id": 6,
+      "parent": 5,
+      "transform": [
+        [40, 0, 0, 0],
+        [0, 40, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    },
+    {
+      "id": 7,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 208, 0, 1]
+      ]
+    },
+    {
+      "id": 8,
+      "parent": 7,
+      "transform": [
+        [40, 0, 0, 0],
+        [0, 40, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    },
+    {
+      "id": 9,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [9, 258, 0, 1]
+      ]
+    },
+    {
+      "id": 10,
+      "parent": 9,
+      "transform": [
+        [40, 0, 0, 0],
+        [0, 40, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+      ],
+      "origin": [0, 0]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/composited-iframe-scroll-repaint-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/composited-iframe-scroll-repaint-expected.txt
new file mode 100644
index 0000000..d7a9f930
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/composited-iframe-scroll-repaint-expected.txt
@@ -0,0 +1,96 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [784, 159],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutIFrame IFRAME",
+      "position": [8, 8],
+      "bounds": [304, 154]
+    },
+    {
+      "name": "LayoutView #document",
+      "position": [10, 10],
+      "bounds": [300, 150],
+      "drawsContent": false,
+      "backgroundColor": "#EEEEEE"
+    },
+    {
+      "name": "Scrolling Layer",
+      "position": [10, 10],
+      "bounds": [300, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [10, 10],
+      "bounds": [300, 516],
+      "backgroundColor": "#EEEEEE",
+      "transform": 1
+    },
+    {
+      "name": "LayoutBlockFlow BODY",
+      "bounds": [284, 500],
+      "transform": 2
+    },
+    {
+      "name": "Overflow Controls Host Layer",
+      "position": [10, 10],
+      "bounds": [300, 150],
+      "drawsContent": false
+    },
+    {
+      "name": "Horizontal Scrollbar Layer",
+      "position": [10, 160],
+      "bounds": [300, 0]
+    },
+    {
+      "name": "Vertical Scrollbar Layer",
+      "position": [310, 10],
+      "bounds": [0, 150]
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -20, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    },
+    {
+      "id": 2,
+      "parent": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [18, 18, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/fixed-img-src-change-after-scroll-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/fixed-img-src-change-after-scroll-expected.txt
new file mode 100644
index 0000000..b2394186
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/fixed-img-src-change-after-scroll-expected.txt
@@ -0,0 +1,41 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [785, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 2016],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "transform": 1
+    },
+    {
+      "name": "LayoutImage (positioned) IMG id='img'",
+      "position": [0, 1050],
+      "bounds": [100, 100],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, -1000, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt
new file mode 100644
index 0000000..04c2902
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/disable-blink-gen-property-trees/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt
@@ -0,0 +1,61 @@
+{
+  "layers": [
+    {
+      "name": "LayoutView #document",
+      "bounds": [800, 600],
+      "drawsContent": false,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "Scrolling Layer",
+      "bounds": [800, 600],
+      "drawsContent": false
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "paintInvalidations": [
+        {
+          "object": "HorizontalScrollbar",
+          "rect": [8, 293, 285, 15],
+          "reason": "scroll control"
+        }
+      ]
+    },
+    {
+      "name": "Ancestor Clipping Layer",
+      "position": [8, 8],
+      "bounds": [285, 285],
+      "drawsContent": false
+    },
+    {
+      "name": "LayoutBlockFlow (positioned) DIV id='container'",
+      "bounds": [600, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "paintInvalidations": [
+        {
+          "object": "LayoutBlockFlow DIV id='inner'",
+          "rect": [0, 0, 600, 600],
+          "reason": "appeared"
+        }
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [-307, 8, 0, 1]
+      ],
+      "flattenInheritedTransform": false
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 9adcccd..3ffbfd9 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -10523,6 +10523,7 @@
     getter baseLayer
     getter depthFar
     getter depthNear
+    getter outputContext
     method constructor
 interface XRRigidTransform
     attribute @@toStringTag
@@ -10538,7 +10539,6 @@
     getter onend
     getter onfocus
     getter onresetpose
-    getter outputContext
     getter renderState
     method cancelAnimationFrame
     method constructor
diff --git a/third_party/blink/web_tests/xr/ar_hittest.html b/third_party/blink/web_tests/xr/ar_hittest.html
index 052f3a99..d589b7ab 100644
--- a/third_party/blink/web_tests/xr/ar_hittest.html
+++ b/third_party/blink/web_tests/xr/ar_hittest.html
@@ -16,10 +16,7 @@
 let fakeDeviceInitParams = { supportsImmersive: false,
                              supportsEnvironmentIntegration: true };
 
-let requestSessionOptions = [ {
-    mode: 'legacy-inline-ar',
-    outputContext: getOutputContext()
-  } ];
+let requestSessionOptions = [ {mode: 'legacy-inline-ar' } ];
 
 let expectedHitMatrix = [1, 0, 0, 1,
                          0, 1, 0, 2,
diff --git a/third_party/blink/web_tests/xr/events_session_resetpose.html b/third_party/blink/web_tests/xr/events_session_resetpose.html
index e3e2407..f1394ce 100644
--- a/third_party/blink/web_tests/xr/events_session_resetpose.html
+++ b/third_party/blink/web_tests/xr/events_session_resetpose.html
@@ -18,13 +18,16 @@
 let fakeDeviceInitParams = { supportsImmersive:true };
 
 let requestSessionOptions = [
-  { outputContext: getOutputContext() },
+  { mode: 'inline' },
   { mode: 'immersive-vr' },
 ];
 
 let testFunction = function(session, t, fakeDeviceController) {
   // Session must have a baseLayer or frame requests will be ignored.
-  session.baseLayer = new XRWebGLLayer(session, gl);
+  session.updateRenderState({
+    baseLayer: new XRWebGLLayer(session, gl),
+    outputContext: getOutputContext()
+  });
 
   let eventWatcher = new EventWatcher(
     t, session, ["resetpose", "watcherdone"]);
diff --git a/third_party/blink/web_tests/xr/exclusive_requestFrame_nolayer.html b/third_party/blink/web_tests/xr/exclusive_requestFrame_nolayer.html
index ed4082eee..7e8222bd 100644
--- a/third_party/blink/web_tests/xr/exclusive_requestFrame_nolayer.html
+++ b/third_party/blink/web_tests/xr/exclusive_requestFrame_nolayer.html
@@ -15,7 +15,7 @@
 
 let requestSessionOptions = [
   { mode: 'immersive-vr' },
-  { outputContext: getOutputContext() }
+  { mode: 'inline' }
 ];
 
 let testFunction = (session) => new Promise((resolve, reject) => {
@@ -37,7 +37,10 @@
   // Wait for a bit and set the baseLayer.
   setTimeout(() => {
     // Once the base layer is set the previously registered callback should run.
-    session.updateRenderState({ baseLayer: webglLayer });
+    session.updateRenderState({
+        baseLayer: webglLayer,
+        outputContext: getOutputContext()
+    });
   }, 300);
 });
 
diff --git a/third_party/blink/web_tests/xr/xrSession_environmentBlendMode.html b/third_party/blink/web_tests/xr/xrSession_environmentBlendMode.html
index 71ffb9ba..003d6fad 100644
--- a/third_party/blink/web_tests/xr/xrSession_environmentBlendMode.html
+++ b/third_party/blink/web_tests/xr/xrSession_environmentBlendMode.html
@@ -15,7 +15,7 @@
 let fakeDeviceInitParams = { supportsImmersive:true };
 
 let requestSessionOptions = [
-  { outputContext: getOutputContext() },
+  { mode: 'inline' },
   { mode: 'immersive-vr' },
 ];
 
diff --git a/third_party/blink/web_tests/xr/xrSession_requestAnimationFrame_timestamp.html b/third_party/blink/web_tests/xr/xrSession_requestAnimationFrame_timestamp.html
index 6fc03e8..85d655cb 100644
--- a/third_party/blink/web_tests/xr/xrSession_requestAnimationFrame_timestamp.html
+++ b/third_party/blink/web_tests/xr/xrSession_requestAnimationFrame_timestamp.html
@@ -17,13 +17,16 @@
 let fakeDeviceInitParams = { supportsImmersive:true };
 
 let requestSessionOptions = [
-  { outputContext: getOutputContext() },
+  { mode: 'inline' },
   { mode: 'immersive-vr' },
 ];
 
 let testFunction = function(session, t, fakeDeviceController) {
   // Session must have a baseLayer or else frame requests will be ignored.
-  session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+  session.updateRenderState({
+    baseLayer: new XRWebGLLayer(session, gl),
+    outputContext: getOutputContext()
+  });
 
   // Need to have a valid pose or input event's don't process.
   fakeDeviceController.setXRPresentationFrameData(
diff --git a/third_party/blink/web_tests/xr/xrStationaryReferenceSpace_floorlevel_updates.html b/third_party/blink/web_tests/xr/xrStationaryReferenceSpace_floorlevel_updates.html
index f8f82b3..dda173184 100644
--- a/third_party/blink/web_tests/xr/xrStationaryReferenceSpace_floorlevel_updates.html
+++ b/third_party/blink/web_tests/xr/xrStationaryReferenceSpace_floorlevel_updates.html
@@ -16,13 +16,16 @@
 let fakeDeviceInitParams = { supportsImmersive:true };
 
 let requestSessionOptions = [
-  { outputContext: getOutputContext() },
+  { mode: 'inline' },
   { mode: 'immersive-vr' },
 ];
 
 let testFunction = function(session, t, fakeDeviceController) {
   // Session must have a baseLayer or else frame requests will be ignored.
-  session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+  session.updateRenderState({
+    baseLayer: new XRWebGLLayer(session, gl),
+    outputContext: getOutputContext()
+  });
 
   fakeDeviceController.setStageTransform(null);
   fakeDeviceController.setXRPresentationFrameData(ORIGIN_POSE, [{
diff --git a/third_party/blink/web_tests/xr/xrWebGLLayer_non_exclusive_adjust_size.html b/third_party/blink/web_tests/xr/xrWebGLLayer_non_exclusive_adjust_size.html
index c6944c1..0b76451 100644
--- a/third_party/blink/web_tests/xr/xrWebGLLayer_non_exclusive_adjust_size.html
+++ b/third_party/blink/web_tests/xr/xrWebGLLayer_non_exclusive_adjust_size.html
@@ -23,15 +23,21 @@
 
 let fakeDeviceInitParams = { supportsImmersive: true };
 
-let requestSessionOptions = [{ outputContext: outputContext }];
+let requestSessionOptions = [{ }];
 
 let testFunction = (session, t) => new Promise((resolve, reject) => {
+  console.log("Test start");
   let webglLayer = new XRWebGLLayer(session, gl);
-  session.updateRenderState({ baseLayer: webglLayer });
+  session.updateRenderState({
+    baseLayer: webglLayer,
+    outputContext: outputContext
+  });
+  console.log("renderState updated");
 
   // Changes to the baseLayer, and thus the baseLayer's framebuffer size, won't
   // take effect until the next frame is processed.
   session.requestAnimationFrame((time, xrFrame) => {
+    console.log("First rAF");
     t.step(() => {
       // The layer's framebuffer should be smaller than the requested size.
       assert_true(webglLayer.framebufferWidth < outputCanvas.width);
@@ -48,6 +54,7 @@
 
     // Give the UA a chance to respond to the resize.
     session.requestAnimationFrame((time, xrFrame) => {
+      console.log("Second rAF");
       // Check to ensure the framebuffer resized to match the new canvas dimensions.
       t.step(() => {
         assert_equals(webglLayer.framebufferWidth, outputCanvas.width);
diff --git a/third_party/blink/web_tests/xr/xrWebGLLayer_opaque_framebuffer.html b/third_party/blink/web_tests/xr/xrWebGLLayer_opaque_framebuffer.html
index 85e0e27..3fe0ecc 100644
--- a/third_party/blink/web_tests/xr/xrWebGLLayer_opaque_framebuffer.html
+++ b/third_party/blink/web_tests/xr/xrWebGLLayer_opaque_framebuffer.html
@@ -15,14 +15,17 @@
 
 let requestSessionOptions = [
   { mode: 'immersive-vr' },
-  { outputContext: getOutputContext() }
+  { mode: 'inline' }
 ];
 
 let testFunction =
   (session, t, fakeDeviceController) => new Promise((resolve, reject) => {
   // Session must have a baseLayer or frame requests will be ignored.
   let webglLayer = new XRWebGLLayer(session, gl);
-  session.updateRenderState({ baseLayer: webglLayer });
+  session.updateRenderState({
+    baseLayer: webglLayer,
+    outputContext: getOutputContext()
+  });
 
   let xrFramebuffer = webglLayer.framebuffer;
 
diff --git a/third_party/sqlite/README.chromium b/third_party/sqlite/README.chromium
index 8ebe022..af2cd26 100644
--- a/third_party/sqlite/README.chromium
+++ b/third_party/sqlite/README.chromium
@@ -155,18 +155,20 @@
 #### Download and unpack the new SQLite release.
 git new-branch sqlite-new-upstream
 # URL from "Alternative Source Code Formats" at https://sqlite.org/download.html
-curl https://sqlite.org/2018/sqlite-src-${NEW}.zip > upstream.zip
+curl https://sqlite.org/2019/sqlite-src-${NEW}.zip > upstream.zip
 mkdir sqlite-src-${NEW}
 unzip ./upstream.zip -d sqlite-src-${NEW}
 rm ./upstream.zip
 mv sqlite-src-${NEW}/sqlite-*/* sqlite-src-${NEW}/
+rm -r sqlite-src-${NEW}/sqlite-*/.fossil-settings
 rmdir sqlite-src-${NEW}/sqlite-*/
+rm -r sqlite-src-${NEW}/compat
 xdg-open sqlite-src-${NEW}  # Make sure everything looks right.
 
 #### Add the new release code in a separate CL, for code review sanity.
 git add sqlite-src-${NEW}  # Committing the code as downloaded, on purpose.
 git clean -i -d -x sqlite-src-${NEW}  # Make sure no file is git-ignored.
-git commit -m "sqlite: Add code for release 3.28.0"
+git commit -m "sqlite: Add code for release ${NEW}"
 git cl upload  # Have the new code in a separate (impossible to review) CL.
 
 #### Create a branch for the old SQLite release's upstream version.
diff --git a/third_party/webxr_test_pages/webxr-samples/360-photos.html b/third_party/webxr_test_pages/webxr-samples/360-photos.html
index 3cf0a6e..ef815ed 100644
--- a/third_party/webxr_test_pages/webxr-samples/360-photos.html
+++ b/third_party/webxr_test_pages/webxr-samples/360-photos.html
@@ -91,14 +91,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -124,13 +117,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -141,7 +128,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         // When rendering 360 photos/videos you want to ensure that the user's
         // head is always at the center of the rendered media. Otherwise users
@@ -167,9 +160,11 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
index 936c574..4d48090 100644
--- a/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/fallback-rendering.html
@@ -91,14 +91,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           // If navigator.xr isn't present in the browser then we need to use
           // the fallback rendering path.
@@ -150,12 +143,7 @@
       }
 
       function onRequestSession() {
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -166,7 +154,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -184,9 +178,11 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html b/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
index 303e2b8d..225e348f 100644
--- a/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
+++ b/third_party/webxr_test_pages/webxr-samples/framebuffer-scaling.html
@@ -118,13 +118,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx  }).then(onSessionStarted);
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then(onSessionStarted);
       }
 
       function onSessionStarted(session) {
@@ -146,6 +140,9 @@
           scene.inputRenderer.setControllerMesh(new Gltf2Node({url: '../media/gltf/controller/controller.gltf'}));
         }
 
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
         // This is the only meaningful change in this sample from xr-presentation.js.
         // It sets a requested scale to be applied to the framebuffer created
         // for the layer. The UA is allowed to ignore the request or adjust it
@@ -153,9 +150,12 @@
         // layer is created, though the UA is allowed to resize the framebuffer
         // at any time.
         let scale = parseFloat(scaleSelect.value);
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl, {
-          framebufferScaleFactor: scale
-        }) });
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl, {
+              framebufferScaleFactor: scale
+            }),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           xrRefSpace = refSpace;
@@ -169,9 +169,12 @@
       }
 
       function onSessionEnded(event) {
-        document.body.removeChild(document.querySelector('#mirror-canvas'));
         xrButton.setSession(null);
 
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
+
         gl = null;
       }
 
diff --git a/third_party/webxr_test_pages/webxr-samples/input-selection.html b/third_party/webxr_test_pages/webxr-samples/input-selection.html
index c81156f..973d0f8 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-selection.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-selection.html
@@ -99,14 +99,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -159,13 +152,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -180,7 +167,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -224,9 +217,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(time, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/input-tracking.html b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
index 4cccd412..c860656 100644
--- a/third_party/webxr_test_pages/webxr-samples/input-tracking.html
+++ b/third_party/webxr_test_pages/webxr-samples/input-tracking.html
@@ -91,17 +91,7 @@
             xrButton.enabled = true;
           });
 
-          // Note: If you don't want dragging on the canvas to do things like
-          // scroll or pull-to-refresh, you'll want to set touch-action: none;
-          // on the canvas' CSS style, which this page does in common.css
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -131,13 +121,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -148,7 +132,16 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        // Note: If you don't want dragging on the canvas to do things like
+        // scroll or pull-to-refresh, you'll want to set touch-action: none;
+        // on the canvas' CSS style, which this page does in common.css
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -167,9 +160,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function updateInputSources(session, frame, refSpace) {
diff --git a/third_party/webxr_test_pages/webxr-samples/magic-window.html b/third_party/webxr_test_pages/webxr-samples/magic-window.html
index 99f7413..faa46a8c 100644
--- a/third_party/webxr_test_pages/webxr-samples/magic-window.html
+++ b/third_party/webxr_test_pages/webxr-samples/magic-window.html
@@ -97,32 +97,14 @@
             xrButton.enabled = true;
           });
 
-          // In order for a non-immersive session to be used we must provide
-          // an outputContext, which indicates the canvas that will contain
-          // results of the session's rendering.
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
           // Pick an arbitrary device for the magic window content and start
           // up a non-immersive session if possible.
-          navigator.xr.requestSession({outputContext: ctx})
-              .then((session) => {
-                // Add the canvas to the document once we know that it will be
-                // rendered to.
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         }
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({mode: 'immersive-vr', outputContext: ctx}).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -141,7 +123,18 @@
           scene.setRenderer(renderer);
         }
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        // In order for a non-immersive session to be used we must provide
+        // an outputContext, which indicates the canvas that will contain
+        // results of the session's rendering.
+        let outputCanvas = document.createElement('canvas');
+
+        // Add the canvas to the document.
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+          baseLayer: new XRWebGLLayer(session, gl),
+          outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           // Since we're dealing with multple sessions now we need to track
@@ -162,10 +155,13 @@
       function onSessionEnded(event) {
         // Only reset the button when the immersive session ends.
         if (event.session.mode.startsWith('immersive')) {
-          // Remove the mirroring canvas.
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        // Remove the output canvas from the document.
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       // Called every time a XRSession requests that a new frame be drawn.
diff --git a/third_party/webxr_test_pages/webxr-samples/mirroring.html b/third_party/webxr_test_pages/webxr-samples/mirroring.html
index b5824f2..29f66b8 100644
--- a/third_party/webxr_test_pages/webxr-samples/mirroring.html
+++ b/third_party/webxr_test_pages/webxr-samples/mirroring.html
@@ -94,22 +94,22 @@
       }
 
       function onRequestSession() {
-        // In order to mirror an exclusive session, we must provide
-        // an XRPresentationContext, which indicates the canvas that will
-        // contain results of the session's rendering.
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-
-        // Add the canvas to the document.
-        document.body.appendChild(mirrorCanvas);
-
-        // Providing an outputContext when requesting an exclusive session
-        // indicates that it should be used as the mirror destination.
         navigator.xr.requestSession({
-          mode: 'immersive-vr',
-          outputContext: ctx
-        }).then(onSessionStarted);
+          mode: 'immersive-vr'
+        }).then((session) => {
+          // In order to mirror an exclusive session, we must provide
+          // an XRPresentationContext, which indicates the canvas that will
+          // contain results of the session's rendering.
+          let outputCanvas = document.createElement('canvas');
+
+          // Add the canvas to the document.
+          document.body.appendChild(outputCanvas);
+
+          // Providing an outputContext to an exclusive session indicates that
+          // it should be used as the mirror destination.
+          session.updateRenderState({ outputContext: outputCanvas.getContext('xrpresent') });
+          onSessionStarted(session).
+        });
       }
 
       function onSessionStarted(session) {
@@ -149,10 +149,12 @@
       function onSessionEnded(event) {
         xrButton.setSession(null);
 
-        gl = null;
+        // Remove the output canvas from the document.
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
 
-        // Remove the injected mirroring canvas from the DOM.
-        document.body.removeChild(document.querySelector('#mirror-canvas'));
+        gl = null;
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/positional-audio.html b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
index a6bf489a..737f972 100644
--- a/third_party/webxr_test_pages/webxr-samples/positional-audio.html
+++ b/third_party/webxr_test_pages/webxr-samples/positional-audio.html
@@ -306,14 +306,7 @@
             scene.addNode(playButton);
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -340,13 +333,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -366,7 +353,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -385,12 +378,15 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
 
           // Stop the audio playback when we exit XR.
           pauseAudio();
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       let draggingSource = null;
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
index e2ac9d4..70eaf4e 100644
--- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
+++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
@@ -140,12 +140,8 @@
               xrButton.setSession(session);
               onSessionStarted(session);
         }).catch(() => {
-            let legacyCanvas = makeCanvas();
-            let legacyCtx = legacyCanvas.getContext('xrpresent');
-            navigator.xr.requestSession({ mode: 'legacy-inline-ar',
-                                          outputContext: legacyCtx
-                                        }).then((session) => {
-                document.body.appendChild(legacyCanvas);
+            navigator.xr.requestSession({ mode: 'legacy-inline-ar' })
+              .then((session) => {
                 xrButton.setSession(session);
                 onSessionStarted(session);
             });
@@ -166,7 +162,13 @@
           scene.setRenderer(renderer);
         }
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = makeCanvas();
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           xrRefSpace = refSpace;
@@ -179,10 +181,10 @@
       }
 
       function onSessionEnded(event) {
-        if (event.session.mode.startsWith('legacy')) {
-          document.body.removeChild(document.querySelector('#legacy-canvas'));
-        }
         xrButton.setSession(null);
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       // Adds a new object to the scene at the
diff --git a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
index ab67574..e36af16 100644
--- a/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
+++ b/third_party/webxr_test_pages/webxr-samples/reduced-bind-rendering.html
@@ -87,14 +87,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -127,13 +120,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -144,7 +131,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = makeCanvas();
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -162,9 +155,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/room-scale.html b/third_party/webxr_test_pages/webxr-samples/room-scale.html
index 488e03f..05bb828 100644
--- a/third_party/webxr_test_pages/webxr-samples/room-scale.html
+++ b/third_party/webxr_test_pages/webxr-samples/room-scale.html
@@ -90,14 +90,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -111,13 +104,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -141,7 +128,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         // Get a stage frame of reference, which will align the user's physical
         // floor with Y=0 and can provide boundaries that indicate where the
@@ -169,9 +162,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
index 38ad602a..d72e646 100644
--- a/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
+++ b/third_party/webxr_test_pages/webxr-samples/spectator-mode.html
@@ -103,11 +103,14 @@
           });
 
           outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
 
-          navigator.xr.requestSession({ outputContext: ctx })
+          navigator.xr.requestSession()
               .then((session) => {
                 document.body.appendChild(outputCanvas);
+                session.updateRenderState({
+                  outputContext: outputCanvas.getContext('xrpresent')
+                });
+
                 onSessionStarted(session);
               });
         } else {
diff --git a/third_party/webxr_test_pages/webxr-samples/stereo-video.html b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
index 8aca66e..76843a3 100644
--- a/third_party/webxr_test_pages/webxr-samples/stereo-video.html
+++ b/third_party/webxr_test_pages/webxr-samples/stereo-video.html
@@ -142,14 +142,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -175,13 +168,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -198,7 +185,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         // In this case we're going to use an eye-level frame of reference
         // because we want to users head to appear in the right place relative
@@ -221,9 +214,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
index 2a4b718..f4188e9a 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/cube-sea.html
@@ -278,13 +278,7 @@
 
           // Set up "magic window" mode.
           if (appSettings.magicWindow) {
-            let outputCanvas = document.createElement('canvas');
-            let ctx = outputCanvas.getContext('xrpresent');
-            navigator.xr.requestSession({outputContext: ctx})
-                .then((session) => {
-                  document.body.appendChild(outputCanvas);
-                  onSessionStarted(session);
-                });
+            navigator.xr.requestSession().then(onSessionStarted);
           }
         } else {
           initFallback();
@@ -315,12 +309,6 @@
 
       function onRequestSession() {
         let xrOptions = { mode: appSettings.arMode ? 'immersive-ar' : 'immersive-vr' };
-        if (appSettings.mirrorCanvas) {
-          let mirrorCanvas = document.createElement('canvas');
-          xrOptions.outputContext = mirrorCanvas.getContext('xrpresent');
-          mirrorCanvas.setAttribute('id', 'mirror-canvas');
-          document.body.appendChild(mirrorCanvas);
-        }
         navigator.xr.requestSession(xrOptions).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
@@ -332,9 +320,16 @@
 
         initGL();
 
-        session.renderState.baseLayer = new XRWebGLLayer(session, gl, {
+        let webglLayer = new XRWebGLLayer(session, gl, {
           framebufferScaleFactor: appSettings.framebufferScale,
           antialias: appSettings.antialias});
+        session.updateRenderState({ baseLayer: webglLayer });
+        
+        if (session.mode.startsWith('immersive') && appSettings.mirrorCanvas) {
+          let outputCanvas = document.createElement('canvas');
+          document.body.appendChild(outputCanvas);
+          session.updateRenderState({ outputContext: outputCanvas.getContext('xrpresent') });
+        }
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -351,10 +346,11 @@
       }
 
       function onSessionEnded(event) {
-        if (event.session.mode.startsWith('immersive') && appSettings.mirrorCanvas) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
-        }
         xrButton.setSession(null);
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       let frameNum = 0;
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
index d8b6cd0..28a239e0 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/offscreen-canvas.html
@@ -89,14 +89,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({outputContext: ctx})
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         }
       }
 
@@ -115,13 +108,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -132,7 +119,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -150,9 +143,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
index 6152c54..8dbd653a 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/permission-request.html
@@ -99,14 +99,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -204,13 +197,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -228,7 +215,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -247,9 +240,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
index 3435a4ae..736fe6fe 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/pointer-painter.html
@@ -88,14 +88,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -122,13 +115,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -143,7 +130,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -220,9 +213,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
index 2041237d..d97e9b5 100644
--- a/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
+++ b/third_party/webxr_test_pages/webxr-samples/tests/sponza.html
@@ -98,14 +98,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({ outputContext: ctx })
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -135,13 +128,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -156,7 +143,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'floor-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -179,9 +172,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
index 8e25d0e..47732d5 100644
--- a/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
+++ b/third_party/webxr_test_pages/webxr-samples/viewport-scaling.html
@@ -115,14 +115,7 @@
             xrButton.enabled = true;
           });
 
-          let outputCanvas = document.createElement('canvas');
-          let ctx = outputCanvas.getContext('xrpresent');
-
-          navigator.xr.requestSession({outputContext: ctx})
-              .then((session) => {
-                document.body.appendChild(outputCanvas);
-                onSessionStarted(session);
-              });
+          navigator.xr.requestSession().then(onSessionStarted);
         } else {
           initFallback();
         }
@@ -151,13 +144,7 @@
       }
 
       function onRequestSession() {
-        // Set up a mirror canvas
-        let mirrorCanvas = document.createElement('canvas');
-        let ctx = mirrorCanvas.getContext('xrpresent');
-        mirrorCanvas.setAttribute('id', 'mirror-canvas');
-        document.body.appendChild(mirrorCanvas);
-
-        navigator.xr.requestSession({ mode: 'immersive-vr', outputContext: ctx }).then((session) => {
+        navigator.xr.requestSession({ mode: 'immersive-vr' }).then((session) => {
           xrButton.setSession(session);
           onSessionStarted(session);
         });
@@ -168,7 +155,13 @@
 
         initGL();
 
-        session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+        let outputCanvas = document.createElement('canvas');
+        document.body.appendChild(outputCanvas);
+
+        session.updateRenderState({
+            baseLayer: new XRWebGLLayer(session, gl),
+            outputContext: outputCanvas.getContext('xrpresent')
+        });
 
         session.requestReferenceSpace({ type: 'stationary', subtype: 'eye-level' }).then((refSpace) => {
           if (session.mode.startsWith('immersive')) {
@@ -186,9 +179,12 @@
 
       function onSessionEnded(event) {
         if (event.session.mode.startsWith('immersive')) {
-          document.body.removeChild(document.querySelector('#mirror-canvas'));
           xrButton.setSession(null);
         }
+
+        if (event.session.renderState.outputContext) {
+          document.body.removeChild(event.session.renderState.outputContext.canvas);
+        }
       }
 
       function onXRFrame(t, frame) {
diff --git a/tools/imagediff/BUILD.gn b/tools/imagediff/BUILD.gn
index 6676d03f..107f6a63 100644
--- a/tools/imagediff/BUILD.gn
+++ b/tools/imagediff/BUILD.gn
@@ -4,7 +4,35 @@
 
 import("//build/symlink.gni")
 
-if (current_toolchain == host_toolchain) {
+# There are three machines involved with building and running tests:
+# * the build host, where GN, ninja, and the build tools run
+# * the test host, where the swarming task runs
+# * the test target, where the test collateral runs
+#
+# image_diff runs on the test host.
+#
+# Normally all three machines can be built for using the same toolchain, but
+# cross-compilation is an important exception. A few of these are:
+# * Linux build host + Windows test host + Windows test target
+# * Linux build host + Linux test host + Android test target
+#
+# Since the concept of a 'test host' toolchain is very rarely needed, GN does
+# not provide it. Instead, determine it here.
+if (target_os == "win" && host_os != "win") {
+  # When targeting Windows from not Windows, use the test target toolchain.
+  image_diff_toolchain = default_toolchain
+} else if (target_os != host_os) {
+  # In common cross-compilation scenarios, the build host matches the test host.
+  # TODO: In chrome/android-on-mac cross builds, image_diff would still have to
+  # be built for Linux and this line is wrong.
+  imagediff_toolchain = host_toolchain
+} else {
+  # When not cross-compiling, use the test target toolchain.
+  imagediff_toolchain = default_toolchain
+}
+
+# If the current toolchain is the test host toolchain, build the tool.
+if (current_toolchain == imagediff_toolchain) {
   executable("imagediff") {
     output_name = "image_diff"  # Different than dir name for historical reasons.
     sources = [
@@ -22,13 +50,21 @@
       "//third_party/zlib",
     ]
   }
-} else {
+  # Otherwise, if the current toolchain is the test target toolchain, make a
+  # symlink to the test host toolchain output so the tests can find it.
+  #
+  # Note that Windows does not follow links when searching for DLLs, so
+  # image_diff.exe from component builds won't run via symlink. Fortunately,
+  # Windows builds use the default_toolchain and so avoid making the link.
+} else if (current_toolchain == default_toolchain &&
+           default_toolchain != imagediff_toolchain) {
   binary_symlink("imagediff") {
-    binary_label = ":$target_name($host_toolchain)"
+    binary_label = ":$target_name($imagediff_toolchain)"
     binary_output_name = "image_diff"
 
-    # The 'executable' target does this automatically.
-    if (is_win) {
+    # For Windows builds, the test host and test target are both Windows and so
+    # binary_symlink needs to use the Windows binary suffix.
+    if (target_os == "win") {
       binary_output_name += ".exe"
     }
   }
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e388a87..7c2ad2e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8159,7 +8159,7 @@
 
 <enum name="ContentResourceType">
   <obsolete>
-    Superseded by ContentResourceType in December 2015 when SUB_RESOURCE was
+    Superseded by ContentResourceType2 in December 2015 when SUB_RESOURCE was
     split into RESOURCE_TYPE_SUB_RESOURCE and RESOURCE_TYPE_PLUGIN_RESOURCE, and
     PING was split into RESOURCE_TYPE_PING and RESOURCE_TYPE_CSP_REPORT.
   </obsolete>
@@ -8200,6 +8200,7 @@
   <int value="15" label="RESOURCE_TYPE_SERVICE_WORKER"/>
   <int value="16" label="RESOURCE_TYPE_CSP_REPORT"/>
   <int value="17" label="RESOURCE_TYPE_PLUGIN_RESOURCE"/>
+  <int value="18" label="RESOURCE_TYPE_NAVIGATION_PRELOAD"/>
 </enum>
 
 <enum name="ContentSetting">
@@ -18032,6 +18033,7 @@
   <int value="1320" label="DECLARATIVENETREQUEST_GETDYNAMICRULES"/>
   <int value="1321" label="AUTOTESTPRIVATE_GETARCSTATE"/>
   <int value="1322" label="AUTOTESTPRIVATE_ISTABLETMODEENABLED"/>
+  <int value="1323" label="AUTOTESTPRIVATE_SETTABLETMODEENABLED"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -31140,6 +31142,8 @@
   <int value="-1618707999" label="enable-webfonts-intervention-v2"/>
   <int value="-1617183455" label="OfflineRecentPages:disabled"/>
   <int value="-1616855537" label="enable-manual-password-generation:disabled"/>
+  <int value="-1615704396"
+      label="AutofillLocalCardMigrationUsesStrikeSystemV2:disabled"/>
   <int value="-1614912400" label="enable-link-disambiguation-popup"/>
   <int value="-1613583483" label="UseNewAcceptLanguageHeader:enabled"/>
   <int value="-1611758030" label="AndroidWebContentsDarkMode:disabled"/>
@@ -32964,6 +32968,8 @@
   <int value="1408331660" label="enhanced-bookmarks-experiment"/>
   <int value="1409437197" label="OfflinePagesLimitlessPrefetching:enabled"/>
   <int value="1410697724" label="mediadrm-enable-non-compositing"/>
+  <int value="1411679884"
+      label="AutofillLocalCardMigrationUsesStrikeSystemV2:enabled"/>
   <int value="1413158119" label="WebRtcRemoteEventLog:disabled"/>
   <int value="1413948819" label="NupPrinting:enabled"/>
   <int value="1416309272" label="only-new-password-form-parsing:enabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 955571a..b4fea67b 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -8877,6 +8877,18 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Autofill.StrikeDatabase.LocalCardMigrationNotOfferedDueToMaxStrikes"
+    enum="AutofillSaveType" expires_after="2020-03-18">
+  <owner>jsaul@google.com</owner>
+  <owner>annelim@google.com</owner>
+  <owner>jiahuiguo@chromium.org</owner>
+  <summary>
+    Records when local card migration is not offered due to the candidate's
+    LocalCardMigration strike count reaching maximum strikes.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave"
     units="strikes" expires_after="2020-03-18">
   <owner>jsaul@google.com</owner>
@@ -8888,6 +8900,17 @@
   </summary>
 </histogram>
 
+<histogram name="Autofill.StrikeDatabase.NthStrikeAdded.LocalCardMigration"
+    units="strikes" expires_after="2020-03-18">
+  <owner>jsaul@google.com</owner>
+  <owner>annelim@google.com</owner>
+  <summary>
+    Records the number of &quot;strikes&quot; a user has, when a prompt for
+    local card migration is dismissed. The strike count is incremented each time
+    the user dismisses the prompt.
+  </summary>
+</histogram>
+
 <histogram name="Autofill.StrikeDatabase.StrikeDatabaseInitFailed"
     units="attempts" expires_after="2020-03-18">
   <owner>jsaul@google.com</owner>
@@ -24315,9 +24338,19 @@
 </histogram>
 
 <histogram name="DnsProbe.ProbeDuration" units="ms">
+  <obsolete>
+    Replaced 2/2019 with DnsProbe.ProbeDuration2, which uses TimeTicks instead
+    of Time.
+  </obsolete>
   <owner>pauljensen@chromium.org</owner>
   <owner>mef@chromium.org</owner>
-  <summary>Time between starting and finishing DNS probe.</summary>
+  <summary>Wall time between starting and finishing DNS probe.</summary>
+</histogram>
+
+<histogram name="DnsProbe.ProbeDuration2" units="ms">
+  <owner>pauljensen@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <summary>Time ticks between starting and finishing DNS probe.</summary>
 </histogram>
 
 <histogram name="DnsProbe.ProbeResult" enum="DnsProbe.ProbeStatus">
@@ -70092,7 +70125,7 @@
 </histogram>
 
 <histogram name="NewTabPage.BackgroundService.Collections.RequestLatency"
-    units="ms" expires_after="M74">
+    units="ms" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70102,7 +70135,7 @@
 </histogram>
 
 <histogram name="NewTabPage.BackgroundService.Images.RequestLatency" units="ms"
-    expires_after="M74">
+    expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70797,7 +70830,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizationAvailability.Backgrounds"
-    enum="NTPBackgroundCustomizationAvailability" expires_after="M74">
+    enum="NTPBackgroundCustomizationAvailability" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70808,7 +70841,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizationAvailability.Shortcuts"
-    enum="NTPShortcutCustomizationAvailability" expires_after="M74">
+    enum="NTPShortcutCustomizationAvailability" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70818,7 +70851,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeAction" enum="NTPCustomizeAction"
-    expires_after="M74">
+    expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70829,7 +70862,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeChromeBackgroundAction"
-    enum="NTPCustomizeChromeBackgroundAction" expires_after="M74">
+    enum="NTPCustomizeChromeBackgroundAction" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70840,7 +70873,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Customized" enum="NTPCustomizedFeatures"
-    expires_after="M74">
+    expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70850,7 +70883,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeLocalImageBackgroundAction"
-    enum="NTPCustomizeLocalImageBackgroundAction" expires_after="M74">
+    enum="NTPCustomizeLocalImageBackgroundAction" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
@@ -70861,7 +70894,7 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeShortcutAction"
-    enum="NTPCustomizeShortcutAction" expires_after="M74">
+    enum="NTPCustomizeShortcutAction" expires_after="M90">
   <owner>ramyan@chromium.org</owner>
   <owner>yyushkina@chromium.org</owner>
   <summary>
diff --git a/tools/perf/benchmarks/system_health_smoke_test.py b/tools/perf/benchmarks/system_health_smoke_test.py
index 4d2b82c..32c1e01 100644
--- a/tools/perf/benchmarks/system_health_smoke_test.py
+++ b/tools/perf/benchmarks/system_health_smoke_test.py
@@ -134,6 +134,9 @@
 
   # crbug.com/903849
   'system_health.memory_mobile/browse:news:cnn:2018',
+
+  # crbug.com/937006
+  'system_health.memory_mobile/browse:news:toi',
 })
 
 
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 274677b..1cb75fa 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -162,6 +162,7 @@
  <item id="ntp_custom_link_checker_request" hash_code="78408551" type="0" deprecated="2018-10-26" content_hash_code="13407730" file_path=""/>
  <item id="ntp_icon_source" hash_code="29197139" type="0" content_hash_code="16399294" os_list="linux,windows" file_path="chrome/browser/search/ntp_icon_source.cc"/>
  <item id="ntp_snippets_fetch" hash_code="15418154" type="0" content_hash_code="10078959" os_list="linux,windows" file_path="components/ntp_snippets/remote/json_request.cc"/>
+ <item id="nux_ntp_background_preview" hash_code="124847649" type="0" content_hash_code="31404656" os_list="linux,windows" file_path="chrome/browser/ui/webui/welcome/nux/ntp_background_fetcher.cc"/>
  <item id="oauth2_access_token_fetcher" hash_code="27915688" type="0" content_hash_code="33501872" os_list="linux,windows" file_path="google_apis/gaia/oauth2_access_token_fetcher_impl.cc"/>
  <item id="oauth2_api_call_flow" hash_code="29188932" type="2" content_hash_code="108831236" os_list="linux,windows" policy_fields="-1" file_path="google_apis/gaia/oauth2_api_call_flow.cc"/>
  <item id="oauth2_mint_token_flow" hash_code="1112842" type="1" second_id="29188932" content_hash_code="91581432" os_list="linux,windows" semantics_fields="1,2,3,4,5" policy_fields="3,4" file_path="google_apis/gaia/oauth2_mint_token_flow.cc"/>
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 179f8642..1b89427 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -2605,8 +2605,8 @@
       ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
   expected_names.push_back(L"ExistingLabel. Annotation");
 
-  // If the status is AnnotationEmpty, failure text should be appended
-  // to the name.
+  // If the status is AnnotationEmpty, no failure text should be added to the
+  // name.
   tree.nodes[7].id = 8;
   tree.nodes[7].role = ax::mojom::Role::kImage;
   tree.nodes[7].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
@@ -2614,7 +2614,7 @@
   tree.nodes[7].SetName("ExistingLabel");
   tree.nodes[7].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
-  expected_names.push_back(L"ExistingLabel. No description is available.");
+  expected_names.push_back(L"ExistingLabel");
 
   // If the status is AnnotationAdult, appropriate text should be appended
   // to the name.
@@ -2625,10 +2625,12 @@
   tree.nodes[8].SetName("ExistingLabel");
   tree.nodes[8].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationAdult);
-  expected_names.push_back(L"ExistingLabel. Appears to be adult content.");
+  expected_names.push_back(
+      L"ExistingLabel. Appears to contain adult content. No description "
+      L"available.");
 
-  // If the status is AnnotationProcessFailed, appropriate text should be
-  // appended to the name.
+  // If the status is AnnotationProcessFailed, no failure text should be added
+  // to the name.
   tree.nodes[9].id = 10;
   tree.nodes[9].role = ax::mojom::Role::kImage;
   tree.nodes[9].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
@@ -2636,7 +2638,7 @@
   tree.nodes[9].SetName("ExistingLabel");
   tree.nodes[9].SetImageAnnotationStatus(
       ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed);
-  expected_names.push_back(L"ExistingLabel. Unable to get a description.");
+  expected_names.push_back(L"ExistingLabel");
 
   // We should have one expected name per child of the root.
   ASSERT_EQ(expected_names.size(), tree.nodes[0].child_ids.size());
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 4605c57..6ef8541 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -381,12 +381,11 @@
           "To get missing image descriptions, open the context menu.");
     case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
       return base::ASCIIToUTF16("Getting description...");
-    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
-      return base::ASCIIToUTF16("No description is available.");
     case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
-      return base::ASCIIToUTF16("Appears to be adult content.");
+      return base::ASCIIToUTF16(
+          "Appears to contain adult content. No description available.");
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
     case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
-      return base::ASCIIToUTF16("Unable to get a description.");
     case ax::mojom::ImageAnnotationStatus::kNone:
     case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
     case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc
index 78c270fb..2d82cc8 100644
--- a/ui/android/delegated_frame_host_android.cc
+++ b/ui/android/delegated_frame_host_android.cc
@@ -198,7 +198,7 @@
     content_layer_->RemoveFromParent();
     content_layer_ = nullptr;
   }
-  if (!HasSavedFrame())
+  if (!HasSavedFrame() || frame_evictor_->visible())
     return;
   std::vector<viz::SurfaceId> surface_ids = {
       viz::SurfaceId(frame_sink_id_, local_surface_id_)};
diff --git a/ui/android/delegated_frame_host_android_unittest.cc b/ui/android/delegated_frame_host_android_unittest.cc
index ad53248..2736d64 100644
--- a/ui/android/delegated_frame_host_android_unittest.cc
+++ b/ui/android/delegated_frame_host_android_unittest.cc
@@ -367,16 +367,15 @@
 // Make sure frame evictor is notified of the newly embedded surface after
 // WasShown.
 TEST_F(DelegatedFrameHostAndroidVizTest, EmbedWhileHidden) {
-  {
-    EXPECT_CALL(client_, WasEvicted());
-    frame_host_->EvictDelegatedFrame();
-  }
+  // Ensure there is currently no frame.
+  frame_host_->WasHidden();
+  frame_host_->EvictDelegatedFrame();
   EXPECT_FALSE(frame_host_->HasSavedFrame());
+
   allocator_.GenerateId();
   viz::LocalSurfaceId id =
       allocator_.GetCurrentLocalSurfaceIdAllocation().local_surface_id();
   gfx::Size size(100, 100);
-  frame_host_->WasHidden();
   frame_host_->EmbedSurface(id, size, cc::DeadlinePolicy::UseDefaultDeadline());
   EXPECT_FALSE(frame_host_->HasSavedFrame());
   frame_host_->WasShown(id, size);
diff --git a/ui/android/java/src/org/chromium/ui/modelutil/PropertyListModel.java b/ui/android/java/src/org/chromium/ui/modelutil/PropertyListModel.java
index 0221a12..de45f3d8 100644
--- a/ui/android/java/src/org/chromium/ui/modelutil/PropertyListModel.java
+++ b/ui/android/java/src/org/chromium/ui/modelutil/PropertyListModel.java
@@ -25,6 +25,12 @@
     }
 
     @Override
+    public void add(int position, T item) {
+        super.add(position, item);
+        item.addObserver(mPropertyObserver);
+    }
+
+    @Override
     public void remove(T item) {
         item.removeObserver(mPropertyObserver);
         super.remove(item);
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 59441b0..5a058e6 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -375,6 +375,7 @@
     "//ui/events",
     "//ui/gfx",
     "//ui/gfx/geometry",
+    "//ui/gl",
     "//ui/gl/init",
   ]
 
diff --git a/ui/aura/demo/DEPS b/ui/aura/demo/DEPS
index 608c21f..58aa4e9 100644
--- a/ui/aura/demo/DEPS
+++ b/ui/aura/demo/DEPS
@@ -2,5 +2,6 @@
   "+components/viz/host",
   "+components/viz/service",  # In-process viz service.
   "+ui/display",          # Windows DPI Initialization.
+  "+ui/gl/gl_switches.h",  # Disable Direct Composition Workaround.
   "+ui/gl/init/gl_factory.h",  # To initialize GL bindings.
 ]
diff --git a/ui/aura/demo/demo_main.cc b/ui/aura/demo/demo_main.cc
index 0282ea5..dcdd568 100644
--- a/ui/aura/demo/demo_main.cc
+++ b/ui/aura/demo/demo_main.cc
@@ -35,6 +35,7 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/skia_util.h"
+#include "ui/gl/gl_switches.h"
 #include "ui/gl/init/gl_factory.h"
 
 #if defined(USE_X11)
@@ -212,6 +213,12 @@
 int main(int argc, char** argv) {
   base::CommandLine::Init(argc, argv);
 
+  // Disabling Direct Composition works around the limitation that
+  // InProcessContextFactory doesn't work with Direct Composition, causing the
+  // window to not render. See http://crbug.com/936249.
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kDisableDirectComposition);
+
   // The exit manager is in charge of calling the dtors of singleton objects.
   base::AtExitManager exit_manager;
 
diff --git a/ui/aura/native_window_occlusion_tracker_win.cc b/ui/aura/native_window_occlusion_tracker_win.cc
index 3d4a226..325ac71 100644
--- a/ui/aura/native_window_occlusion_tracker_win.cc
+++ b/ui/aura/native_window_occlusion_tracker_win.cc
@@ -302,6 +302,15 @@
     return;
   // Set up initial conditions for occlusion calculation.
   bool all_minimized = true;
+
+  // Compute the SkRegion for the screen.
+  int screen_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
+  int screen_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
+  SkRegion screen_region = SkRegion(
+      SkIRect::MakeLTRB(screen_left, screen_top,
+                        screen_left + GetSystemMetrics(SM_CXVIRTUALSCREEN),
+                        screen_top + GetSystemMetrics(SM_CYVIRTUALSCREEN)));
+
   for (auto& root_window_pair : root_window_hwnds_occlusion_state_) {
     root_window_pair.second.unoccluded_region.setEmpty();
     HWND hwnd = root_window_pair.first;
@@ -317,6 +326,10 @@
         root_window_pair.second.unoccluded_region =
             SkRegion(SkIRect::MakeLTRB(window_rect.left, window_rect.top,
                                        window_rect.right, window_rect.bottom));
+        // Clip the unoccluded region by the screen dimensions, to handle the
+        // case of the app window being partly off screen.
+        root_window_pair.second.unoccluded_region.op(screen_region,
+                                                     SkRegion::kIntersect_Op);
       }
       // If call to GetWindowRect fails, window will be treated as occluded,
       // because unoccluded_region will be empty.
diff --git a/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc b/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
index e66f1369..b028d335 100644
--- a/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
+++ b/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
@@ -123,10 +123,13 @@
                        GetWindowLong(hwnd, GWL_EXSTYLE));
 
     // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
-    // moved part of it offscreen.
+    // moved part of it offscreen. But, if the original requested bounds are
+    // offscreen, don't adjust the position.
     gfx::Rect window_bounds(wr);
-    window_bounds.set_x(std::max(0, window_bounds.x()));
-    window_bounds.set_y(std::max(0, window_bounds.y()));
+    if (bounds.x() >= 0)
+      window_bounds.set_x(std::max(0, window_bounds.x()));
+    if (bounds.y() >= 0)
+      window_bounds.set_y(std::max(0, window_bounds.y()));
     SetWindowPos(hwnd, HWND_TOP, window_bounds.x(), window_bounds.y(),
                  window_bounds.width(), window_bounds.height(),
                  SWP_NOREPOSITION);
@@ -191,6 +194,38 @@
   EXPECT_FALSE(observer.is_expecting_call());
 }
 
+// Simple test partially covering an aura window with a native window.
+TEST_F(NativeWindowOcclusionTrackerTest, PartialOcclusion) {
+  base::RunLoop run_loop;
+
+  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
+  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
+  observer.set_expectation(Window::OcclusionState::VISIBLE);
+  CreateNativeWindowWithBounds(gfx::Rect(0, 0, 50, 50));
+  run_loop.Run();
+  EXPECT_FALSE(observer.is_expecting_call());
+}
+
+// Simple test that a partly off screen aura window, with the on screen part
+// occluded by a native window, is considered occluded.
+TEST_F(NativeWindowOcclusionTrackerTest, OffscreenOcclusion) {
+  base::RunLoop run_loop;
+
+  MockWindowTreeHostObserver observer(run_loop.QuitClosure());
+  CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
+
+  // Move the tracked window 50 pixels offscreen to the left.
+  int screen_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
+  SetWindowPos(host()->GetAcceleratedWidget(), HWND_TOP, screen_left - 50, 0,
+               100, 100, SWP_NOZORDER | SWP_NOSIZE);
+
+  // Create a native window that covers the onscreen part of the tracked window.
+  CreateNativeWindowWithBounds(gfx::Rect(screen_left, 0, 50, 100));
+  observer.set_expectation(Window::OcclusionState::OCCLUDED);
+  run_loop.Run();
+  EXPECT_FALSE(observer.is_expecting_call());
+}
+
 // Simple test with an aura window and native window that do not overlap.
 TEST_F(NativeWindowOcclusionTrackerTest, SimpleVisible) {
   base::RunLoop run_loop;
diff --git a/ui/views/examples/BUILD.gn b/ui/views/examples/BUILD.gn
index f665273a..4f311a7 100644
--- a/ui/views/examples/BUILD.gn
+++ b/ui/views/examples/BUILD.gn
@@ -124,6 +124,7 @@
     "//ui/compositor",
     "//ui/compositor:test_support",
     "//ui/gfx",
+    "//ui/gl",
     "//ui/gl/init",
     "//ui/resources:ui_test_pak",
     "//ui/views",
diff --git a/ui/views/examples/DEPS b/ui/views/examples/DEPS
index 38ff08c..36a503a 100644
--- a/ui/views/examples/DEPS
+++ b/ui/views/examples/DEPS
@@ -4,6 +4,7 @@
   "+content/public",
   "+content/shell",
   "+sandbox",
+  "+ui/gl/gl_switches.h",  # Disable Direct Composition Workaround.
   "+ui/gl/init/gl_factory.h",  # To initialize GL bindings.
   "+ui/views_content_client",
 ]
diff --git a/ui/views/examples/examples_main.cc b/ui/views/examples/examples_main.cc
index 5eda9d5..fe4d93c 100644
--- a/ui/views/examples/examples_main.cc
+++ b/ui/views/examples/examples_main.cc
@@ -26,6 +26,7 @@
 #include "ui/base/ui_base_paths.h"
 #include "ui/compositor/test/in_process_context_factory.h"
 #include "ui/display/screen.h"
+#include "ui/gl/gl_switches.h"
 #include "ui/gl/init/gl_factory.h"
 #include "ui/views/examples/example_base.h"
 #include "ui/views/examples/examples_window.h"
@@ -59,6 +60,12 @@
 
   base::CommandLine::Init(argc, argv);
 
+  // Disabling Direct Composition works around the limitation that
+  // InProcessContextFactory doesn't work with Direct Composition, causing the
+  // window to not render. See http://crbug.com/936249.
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kDisableDirectComposition);
+
   base::AtExitManager at_exit;
 
 #if defined(USE_X11)
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 6fa8cd0..3703f16 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -3021,17 +3021,15 @@
   event.latency()->AddLatencyNumberWithTimestamp(
       ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time, 1);
 
+  if (event_type == ui::ET_TOUCH_RELEASED)
+    id_generator_.ReleaseNumber(pointer_id);
+
   // There are cases where the code handling the message destroys the
   // window, so use the weak ptr to check if destruction occurred or not.
   base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr());
   delegate_->HandleTouchEvent(&event);
 
   if (ref) {
-    // Release the pointer id only when |HWNDMessageHandler| and |id_generator_|
-    // are not destroyed.
-    if (event_type == ui::ET_TOUCH_RELEASED)
-      id_generator_.ReleaseNumber(pointer_id);
-
     // Mark touch released events handled. These will usually turn into tap
     // gestures, and doing this avoids propagating the event to other windows.
     if (delegate_->GetFrameMode() == FrameMode::SYSTEM_DRAWN) {