diff --git a/DEPS b/DEPS
index 9482f5eb..8166e86 100644
--- a/DEPS
+++ b/DEPS
@@ -129,11 +129,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'be2062c4305f05b4d29239e89dd7ab5108cbb7e1',
+  'skia_revision': '3d50730e1246d9350e5fdc3f356cfb235fe9e7e7',
   # 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': '81260d20b1cfb15752913acb657113c92c6c0026',
+  'v8_revision': '6abaac07e38513482384d5014c968d47c1159466',
   # 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.
@@ -141,11 +141,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': '639729c3e65e7d4c127bebf5a288ad31918dba8e',
+  'angle_revision': '2664da8beb55839f3f50b11c846960b0d15dc4f7',
   # 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': '428c645874c29a1116b775ed73ff780e086a9150',
+  'swiftshader_revision': 'f993de30e53967ca65e0ee7d1c232a370515ad91',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -252,7 +252,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '102e430a88dbf15e1863aafe41dfed406e2394fe',
+  'spv_tools_revision': '3335c61147d76b9282aaee1a499bb40ca71905ac',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -268,7 +268,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'bff933affcffd6d907c5356237668ba755d4e266',
+  'dawn_revision': '2ec74dcc3f75336cfd99a428e5f895cafc03a96f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -292,7 +292,7 @@
   # Also, if you change these, update buildtools/DEPS too. Also update the
   # libc++ svn_revision in //buildtools/deps_revisions.gni.
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
-  'libcxx_revision': 'fbddc46986100095d5f7ed1bc2bf795d3bb3e9e4',
+  'libcxx_revision': '9b96c3dbd4e89c10d9fd8364da4b65f93c6f4276',
   'libcxxabi_revision': '0d529660e32d77d9111912d73f2c74fc5fa2a858',
   'libunwind_revision': '69d9b84cca8354117b9fe9705a4430d789ee599b',
 }
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0d4500b93834267bf2ba73404c4c944015603b86',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '821ab18888033c8141e33eba48980494a635ff40',
       'condition': 'checkout_linux',
   },
 
@@ -1183,7 +1183,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'b2622a5caffbd4aae2e7137224471e555bfd1834',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '5b2d700cf0d39328f3b213e17193d08837e8d8ad',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1256,7 +1256,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'SlcbUnEufAQ-iuOwGOl8yYQuctmpf7bMqh59kBfpil0C',
+              'version': 'BReCwfbVwCNM2Ry4QpnrwlE3Y5gPJ2rRoyMbxFS0-4UC',
           },
       ],
       'condition': 'checkout_android',
@@ -1354,7 +1354,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '3f6583d3fee4ab71866ade794504a20eb6f63f88',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ff7730d2ba2b1d2fcf9ed55f51f20b8256d9c31d',
+    Var('webrtc_git') + '/src.git' + '@' + '2f92b414aeb96a5db181195a7bc0512cdae18dbd',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1395,7 +1395,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e33c9cf2e4ee04c6ad5adb2c7a4de0dc626fe5fe',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2f94c859caf2b1a47c8c5441539ea1a58599c8ec',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index d12491a47..dce208c 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1766,12 +1766,13 @@
     'webauthn': {
         'filepath': 'chrome/android/java/src/org/chromium/chrome/browser/webauth/'\
                     '|chrome/android/javatests/src/org/chromium/chrome/browser/webauth/'\
-                    '|chrome/browser/webauthn/'\
                     '|chrome/browser/ui/webauthn/'\
                     '|chrome/browser/ui/views/webauthn/'\
+                    '|chrome/browser/webauthn/'\
                     '|content/browser/webauth/'\
                     '|device/fido/'\
-                    '|third_party/blink/public/platform/modules/webauth/',
+                    '|third_party/blink/public/mojom/webauthn/'\
+                    '|third_party/microsoft_webauthn/',
     },
     'webgpu': {
         'filepath': 'third_party/blink/renderer/modules/webgpu/',
diff --git a/android_webview/browser/gfx/hardware_renderer.cc b/android_webview/browser/gfx/hardware_renderer.cc
index a3b2503..d00b6ef 100644
--- a/android_webview/browser/gfx/hardware_renderer.cc
+++ b/android_webview/browser/gfx/hardware_renderer.cc
@@ -84,8 +84,9 @@
   child_frame_queue_.emplace_back(std::move(child_frames.front()));
 }
 
-void HardwareRenderer::DrawGL(HardwareRendererDrawParams* params) {
-  TRACE_EVENT0("android_webview", "HardwareRenderer::DrawGL");
+void HardwareRenderer::Draw(HardwareRendererDrawParams* params) {
+  TRACE_EVENT1("android_webview", "HardwareRenderer::Draw", "vulkan",
+               surfaces_->is_using_vulkan());
 
   for (auto& pruned_frame : WaitAndPruneFrameQueue(&child_frame_queue_))
     ReturnChildFrame(std::move(pruned_frame));
@@ -103,7 +104,7 @@
     // We need to watch if the current Android context has changed and enforce a
     // clean-up in the compositor.
     EGLContext current_context = eglGetCurrentContext();
-    DCHECK(current_context) << "DrawGL called without EGLContext";
+    DCHECK(current_context) << "Draw called without EGLContext";
 
     // TODO(boliu): Handle context loss.
     if (last_egl_context_ != current_context)
diff --git a/android_webview/browser/gfx/hardware_renderer.h b/android_webview/browser/gfx/hardware_renderer.h
index 3c2849a7..13b3bbf9 100644
--- a/android_webview/browser/gfx/hardware_renderer.h
+++ b/android_webview/browser/gfx/hardware_renderer.h
@@ -57,7 +57,7 @@
   explicit HardwareRenderer(RenderThreadManager* state);
   ~HardwareRenderer() override;
 
-  void DrawGL(HardwareRendererDrawParams* params);
+  void Draw(HardwareRendererDrawParams* params);
   void CommitFrame();
 
  private:
diff --git a/android_webview/browser/gfx/render_thread_manager.cc b/android_webview/browser/gfx/render_thread_manager.cc
index eca5496..14ddbf2 100644
--- a/android_webview/browser/gfx/render_thread_manager.cc
+++ b/android_webview/browser/gfx/render_thread_manager.cc
@@ -192,7 +192,7 @@
   }
 
   if (hardware_renderer_)
-    hardware_renderer_->DrawGL(params);
+    hardware_renderer_->Draw(params);
 }
 
 void RenderThreadManager::DestroyHardwareRendererOnRT(bool save_restore) {
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 025ec3a..a7423173 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -39,6 +39,7 @@
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.FieldTrialList;
+import org.chromium.base.JNIUtils;
 import org.chromium.base.PathService;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
@@ -134,6 +135,8 @@
 
             final Context context = ContextUtils.getApplicationContext();
 
+            JNIUtils.setClassLoader(WebViewChromiumAwInit.class.getClassLoader());
+
             // We are rewriting Java resources in the background.
             // NOTE: Any reference to Java resources will cause a crash.
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 3ab4f14..6da0701c 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -3219,7 +3219,7 @@
 
     @CalledByNative
     private void updateScrollState(int maxContainerViewScrollOffsetX,
-            int maxContainerViewScrollOffsetY, int contentWidthDip, int contentHeightDip,
+            int maxContainerViewScrollOffsetY, float contentWidthDip, float contentHeightDip,
             float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor) {
         mContentWidthDip = contentWidthDip;
         mContentHeightDip = contentHeightDip;
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index cba5aad4..27917b7 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -665,6 +665,7 @@
 void AppListControllerImpl::OnHomeLauncherAnimationComplete(
     bool shown,
     int64_t display_id) {
+  ResetHomeLauncherIfShown();
   CloseAssistantUi(shown ? AssistantExitPoint::kLauncherOpen
                          : AssistantExitPoint::kLauncherClose);
 }
@@ -1154,7 +1155,8 @@
   auto* const keyboard_controller = keyboard::KeyboardController::Get();
   if (keyboard_controller->IsKeyboardVisible())
     keyboard_controller->HideKeyboardByUser();
-  presenter_.GetView()->CloseOpenedPage();
+
+  presenter_.GetView()->ResetForHomeLauncherShow();
 
   // Refresh the suggestion chips with empty query.
   StartSearch(base::string16());
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 05d2da1..78c260ed 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -404,6 +404,11 @@
   RecordFolderMetrics();
 }
 
+void AppListView::ResetForHomeLauncherShow() {
+  GetInitiallyFocusedView()->RequestFocus();
+  CloseOpenedPage();
+}
+
 void AppListView::SetDragAndDropHostOfCurrentAppList(
     ApplicationDragAndDropHost* drag_and_drop_host) {
   app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index 922a947..e1a6672 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -133,6 +133,9 @@
   // fullscreen app list feature is set.
   void Initialize(const InitParams& params);
 
+  // Resets AppListView to be re-shown after being dismissed.
+  void ResetForHomeLauncherShow();
+
   // If |drag_and_drop_host| is not NULL it will be called upon drag and drop
   // operations outside the application list. This has to be called after
   // Initialize was called since the app list object needs to exist so that
diff --git a/ash/media/media_notification_background.cc b/ash/media/media_notification_background.cc
index 5fe4f5a..1a8a2a3 100644
--- a/ash/media/media_notification_background.cc
+++ b/ash/media/media_notification_background.cc
@@ -12,6 +12,7 @@
 #include "ui/gfx/color_analysis.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/scoped_canvas.h"
+#include "ui/views/style/typography.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -337,6 +338,17 @@
   owner_->SchedulePaint();
 }
 
+SkColor MediaNotificationBackground::GetBackgroundColor() const {
+  return background_color_.value_or(kMediaNotificationDefaultBackgroundColor);
+}
+
+SkColor MediaNotificationBackground::GetForegroundColor() const {
+  return color_utils::GetColorWithMinimumContrast(
+      foreground_color_.value_or(views::style::GetColor(
+          *owner_, views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY)),
+      GetBackgroundColor());
+}
+
 int MediaNotificationBackground::GetArtworkWidth(
     const gfx::Size& view_size) const {
   if (artwork_.isNull())
diff --git a/ash/media/media_notification_background.h b/ash/media/media_notification_background.h
index 74fbc92d..6f9245a 100644
--- a/ash/media/media_notification_background.h
+++ b/ash/media/media_notification_background.h
@@ -41,6 +41,9 @@
   void UpdateArtwork(const gfx::ImageSkia& image);
   void UpdateArtworkMaxWidthPct(double max_width_pct);
 
+  SkColor GetBackgroundColor() const;
+  SkColor GetForegroundColor() const;
+
  private:
   friend class MediaNotificationBackgroundTest;
   friend class MediaNotificationViewTest;
diff --git a/ash/media/media_notification_view.cc b/ash/media/media_notification_view.cc
index ace6fe00..6fc17513 100644
--- a/ash/media/media_notification_view.cc
+++ b/ash/media/media_notification_view.cc
@@ -62,16 +62,34 @@
     MediaSessionAction::kSeekBackward,  MediaSessionAction::kSeekForward,
 };
 
-SkColor GetMediaNotificationColor(const views::View& view) {
-  return views::style::GetColor(view, views::style::CONTEXT_LABEL,
-                                views::style::STYLE_PRIMARY);
-}
-
 void RecordMetadataHistogram(MediaNotificationView::Metadata metadata) {
   UMA_HISTOGRAM_ENUMERATION(MediaNotificationView::kMetadataHistogramName,
                             metadata);
 }
 
+const gfx::VectorIcon* GetVectorIconForMediaAction(MediaSessionAction action) {
+  switch (action) {
+    case MediaSessionAction::kPreviousTrack:
+      return &vector_icons::kMediaPreviousTrackIcon;
+    case MediaSessionAction::kSeekBackward:
+      return &vector_icons::kMediaSeekBackwardIcon;
+    case MediaSessionAction::kPlay:
+      return &vector_icons::kPlayArrowIcon;
+    case MediaSessionAction::kPause:
+      return &vector_icons::kPauseIcon;
+    case MediaSessionAction::kSeekForward:
+      return &vector_icons::kMediaSeekForwardIcon;
+    case MediaSessionAction::kNextTrack:
+      return &vector_icons::kMediaNextTrackIcon;
+    case MediaSessionAction::kStop:
+    case MediaSessionAction::kSkipAd:
+      NOTREACHED();
+      break;
+  }
+
+  return nullptr;
+}
+
 }  // namespace
 
 // static
@@ -147,33 +165,23 @@
       views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER);
   main_row_->AddChildView(button_row_);
 
-  CreateMediaButton(vector_icons::kMediaPreviousTrackIcon,
-                    MediaSessionAction::kPreviousTrack);
-  CreateMediaButton(vector_icons::kMediaSeekBackwardIcon,
-                    MediaSessionAction::kSeekBackward);
+  CreateMediaButton(MediaSessionAction::kPreviousTrack);
+  CreateMediaButton(MediaSessionAction::kSeekBackward);
 
   // |play_pause_button_| toggles playback.
   play_pause_button_ = views::CreateVectorToggleImageButton(this);
   play_pause_button_->set_tag(static_cast<int>(MediaSessionAction::kPlay));
   play_pause_button_->SetPreferredSize(kMediaButtonSize);
-  SkColor play_button_color = GetMediaNotificationColor(*play_pause_button_);
-  views::SetImageFromVectorIcon(play_pause_button_,
-                                vector_icons::kPlayArrowIcon,
-                                kMediaButtonIconSize, play_button_color);
-  views::SetToggledImageFromVectorIcon(play_pause_button_,
-                                       vector_icons::kPauseIcon,
-                                       kMediaButtonIconSize, play_button_color);
   button_row_->AddChildView(play_pause_button_);
 
-  CreateMediaButton(vector_icons::kMediaSeekForwardIcon,
-                    MediaSessionAction::kSeekForward);
-  CreateMediaButton(vector_icons::kMediaNextTrackIcon,
-                    MediaSessionAction::kNextTrack);
+  CreateMediaButton(MediaSessionAction::kSeekForward);
+  CreateMediaButton(MediaSessionAction::kNextTrack);
 
   SetBackground(std::make_unique<MediaNotificationBackground>(
       this, message_center::kNotificationCornerRadius,
       message_center::kNotificationCornerRadius, kMediaImageMaxWidthPct));
 
+  UpdateForegroundColor();
   UpdateControlButtonsVisibilityWithNotification(notification);
   UpdateCornerRadius(message_center::kNotificationCornerRadius,
                      message_center::kNotificationCornerRadius);
@@ -318,6 +326,8 @@
 
   UMA_HISTOGRAM_BOOLEAN(kArtworkHistogramName, has_artwork_);
 
+  UpdateForegroundColor();
+
   PreferredSizeChanged();
   Layout();
   SchedulePaint();
@@ -390,12 +400,9 @@
   UpdateActionButtonsVisibility();
 }
 
-void MediaNotificationView::CreateMediaButton(const gfx::VectorIcon& icon,
-                                              MediaSessionAction action) {
+void MediaNotificationView::CreateMediaButton(MediaSessionAction action) {
   views::ImageButton* button = views::CreateVectorImageButton(this);
   button->set_tag(static_cast<int>(action));
-  views::SetImageFromVectorIcon(button, icon, kMediaButtonIconSize,
-                                GetMediaNotificationColor(*button));
   button->SetPreferredSize(kMediaButtonSize);
   button_row_->AddChildView(button);
 }
@@ -442,4 +449,52 @@
   return visible_actions;
 }
 
+void MediaNotificationView::UpdateForegroundColor() {
+  const SkColor background =
+      GetMediaNotificationBackground()->GetBackgroundColor();
+  const SkColor foreground =
+      GetMediaNotificationBackground()->GetForegroundColor();
+
+  title_label_->SetEnabledColor(foreground);
+  artist_label_->SetEnabledColor(foreground);
+  header_row_->SetAccentColor(foreground);
+
+  title_label_->SetBackgroundColor(background);
+  artist_label_->SetBackgroundColor(background);
+  header_row_->SetBackgroundColor(background);
+
+  // Update play/pause button images.
+  views::SetImageFromVectorIcon(
+      play_pause_button_,
+      *GetVectorIconForMediaAction(MediaSessionAction::kPlay),
+      kMediaButtonIconSize, foreground);
+  views::SetToggledImageFromVectorIcon(
+      play_pause_button_,
+      *GetVectorIconForMediaAction(MediaSessionAction::kPause),
+      kMediaButtonIconSize, foreground);
+
+  // Update action buttons.
+  for (int i = 0; i < button_row_->child_count(); ++i) {
+    views::View* child = button_row_->child_at(i);
+
+    // Skip the play pause button since it is a special case.
+    if (child == play_pause_button_)
+      continue;
+
+    // Skip if the view is not an image button.
+    if (child->GetClassName() != views::ImageButton::kViewClassName)
+      continue;
+
+    views::ImageButton* button = static_cast<views::ImageButton*>(child);
+
+    views::SetImageFromVectorIcon(
+        button,
+        *GetVectorIconForMediaAction(
+            static_cast<MediaSessionAction>(button->tag())),
+        kMediaButtonIconSize, foreground);
+
+    button->SchedulePaint();
+  }
+}
+
 }  // namespace ash
diff --git a/ash/media/media_notification_view.h b/ash/media/media_notification_view.h
index e985d09..19539293 100644
--- a/ash/media/media_notification_view.h
+++ b/ash/media/media_notification_view.h
@@ -95,10 +95,9 @@
   void UpdateControlButtonsVisibilityWithNotification(
       const message_center::Notification& notification);
 
-  // Creates an image button with |icon| and adds it to |button_row_|. When
-  // clicked it will trigger |action| on the sesssion.
-  void CreateMediaButton(const gfx::VectorIcon& icon,
-                         media_session::mojom::MediaSessionAction action);
+  // Creates an image button with an icon that matches |action| and adds it
+  // to |button_row_|. When clicked it will trigger |action| on the session.
+  void CreateMediaButton(media_session::mojom::MediaSessionAction action);
 
   void UpdateActionButtonsVisibility();
   void UpdateViewForExpandedState();
@@ -111,6 +110,8 @@
   std::set<media_session::mojom::MediaSessionAction> CalculateVisibleActions(
       bool expanded) const;
 
+  void UpdateForegroundColor();
+
   // View containing close and settings buttons.
   std::unique_ptr<message_center::NotificationControlButtonsView>
       control_buttons_view_;
diff --git a/ash/media/media_notification_view_unittest.cc b/ash/media/media_notification_view_unittest.cc
index f0785b0..5d90f707 100644
--- a/ash/media/media_notification_view_unittest.cc
+++ b/ash/media/media_notification_view_unittest.cc
@@ -675,11 +675,12 @@
 
 TEST_F(MediaNotificationViewTest, UpdateArtworkFromItem) {
   int title_artist_width = title_artist_row()->width();
+  const SkColor accent = header_row()->accent_color_for_testing();
   gfx::Size size = view()->size();
 
   SkBitmap image;
   image.allocN32Pixels(10, 10);
-  image.eraseColor(SK_ColorWHITE);
+  image.eraseColor(SK_ColorMAGENTA);
 
   EXPECT_TRUE(GetArtworkImage().isNull());
 
@@ -697,6 +698,7 @@
   EXPECT_FALSE(GetArtworkImage().isNull());
   EXPECT_EQ(gfx::Size(10, 10), GetArtworkImage().size());
   EXPECT_EQ(size, view()->size());
+  EXPECT_NE(accent, header_row()->accent_color_for_testing());
 
   GetItem()->MediaControllerImageChanged(
       media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
@@ -711,6 +713,7 @@
   // affected.
   EXPECT_TRUE(GetArtworkImage().isNull());
   EXPECT_EQ(size, view()->size());
+  EXPECT_EQ(accent, header_row()->accent_color_for_testing());
 }
 
 TEST_F(MediaNotificationViewTest, UpdateIconFromItem) {
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index c8622b9..eb5f904d 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -532,6 +532,8 @@
 
 void OverviewSession::ResetDraggedWindowGesture() {
   window_drag_controller_->ResetGesture();
+  for (std::unique_ptr<OverviewGrid>& grid : grid_list_)
+    grid->OnSelectorItemDragEnded();
 }
 
 void OverviewSession::OnWindowDragStarted(aura::Window* dragged_window,
diff --git a/base/android/java/src/org/chromium/base/JNIUtils.java b/base/android/java/src/org/chromium/base/JNIUtils.java
index 3fcec91..1b53b9f06 100644
--- a/base/android/java/src/org/chromium/base/JNIUtils.java
+++ b/base/android/java/src/org/chromium/base/JNIUtils.java
@@ -13,6 +13,7 @@
 @MainDex
 public class JNIUtils {
     private static Boolean sSelectiveJniRegistrationEnabled;
+    private static ClassLoader sJniClassLoader;
 
     /**
      * This returns a ClassLoader that is capable of loading Chromium Java code. Such a ClassLoader
@@ -21,7 +22,18 @@
      */
     @CalledByNative
     public static Object getClassLoader() {
-        return JNIUtils.class.getClassLoader();
+        if (sJniClassLoader == null) {
+            return JNIUtils.class.getClassLoader();
+        }
+        return sJniClassLoader;
+    }
+
+    /**
+     * Sets the ClassLoader to be used for loading Java classes from native.
+     * @param classLoader the ClassLoader to use.
+     */
+    public static void setClassLoader(ClassLoader classLoader) {
+        sJniClassLoader = classLoader;
     }
 
     /**
diff --git a/base/win/pe_image_test.cc b/base/win/pe_image_test.cc
index 8591495..3ce402f 100644
--- a/base/win/pe_image_test.cc
+++ b/base/win/pe_image_test.cc
@@ -5,7 +5,7 @@
 #include <windows.h>
 
 #include <cfgmgr32.h>
-#include <shellapi.h>
+#include <shlobj.h>
 
 #pragma comment(linker, "/export:FwdExport=KERNEL32.CreateFileA")
 
@@ -22,8 +22,9 @@
   CM_MapCrToWin32Err(CR_SUCCESS, ERROR_SUCCESS);
 
   // Call into shell32.dll.
-  SHFILEOPSTRUCT file_operation = {0};
-  SHFileOperation(&file_operation);
+  PWSTR path = nullptr;
+  if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Public, 0, nullptr, &path)))
+    CoTaskMemFree(path);
 
   // Call into kernel32.dll.
   HANDLE h = CreateEvent(NULL, FALSE, FALSE, NULL);
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index e6ee0ab..91f6851c 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -1132,6 +1132,25 @@
     print _GenerateAvailableDevicesMessage(self.devices)
 
 
+class _PackageInfoCommand(_Command):
+  name = 'package-info'
+  # TODO(ntfschr): Support this by figuring out how to construct
+  # self.apk_helper for bundles (http://crbug.com/952443).
+  description = 'Show various attributes of this APK.'
+  need_device_args = False
+  needs_package_name = True
+  needs_apk_path = True
+
+  def Run(self):
+    # Format all (even ints) as strings, to handle cases where APIs return None
+    print 'Package name: "%s"' % self.args.package_name
+    print 'versionCode: %s' % self.apk_helper.GetVersionCode()
+    print 'versionName: "%s"' % self.apk_helper.GetVersionName()
+    print 'minSdkVersion: %s' % self.apk_helper.GetMinSdkVersion()
+    print 'targetSdkVersion: "%s"' % self.apk_helper.GetTargetSdkVersion()
+    print 'Supported ABIs: %r' % self.apk_helper.GetAbis()
+
+
 class _InstallCommand(_Command):
   name = 'install'
   description = 'Installs the APK or bundle to one or more devices.'
@@ -1184,7 +1203,7 @@
   def Run(self):
     if self.is_bundle:
       # TODO(ntfschr): Support this by figuring out how to construct
-      # self.apk_helper for bundles.
+      # self.apk_helper for bundles (http://crbug.com/952443).
       raise Exception(
           'Switching WebView providers not supported for bundles yet!')
     if not _IsWebViewProvider(self.apk_helper):
@@ -1532,6 +1551,7 @@
 # Shared commands for regular APKs and app bundles.
 _COMMANDS = [
     _DevicesCommand,
+    _PackageInfoCommand,
     _InstallCommand,
     _UninstallCommand,
     _SetWebViewProviderCommand,
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index c5f7cc8c..6dd2018 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -1313,12 +1313,12 @@
 
     # Deps to add to the compile-time classpath (but not the runtime classpath).
     # TODO(agrieve): Might be less confusing to fold these into bootclasspath.
-    javac_extra_jars = [c['unprocessed_jar_path']
-                  for c in classpath_deps.Direct('java_library')]
-    extra_jars = [c['jar_path']
-                  for c in classpath_deps.Direct('java_library')]
+    javac_extra_jars = [
+        c['unprocessed_jar_path'] for c in classpath_deps.All('java_library')
+    ]
+    extra_jars = [c['jar_path'] for c in classpath_deps.All('java_library')]
     interface_extra_jars = [
-        c['interface_jar_path'] for c in classpath_deps.Direct('java_library')
+        c['interface_jar_path'] for c in classpath_deps.All('java_library')
     ]
 
     # These are jars specified by input_jars_paths that almost never change.
diff --git a/buildtools/DEPS b/buildtools/DEPS
index dc241c18..55313b8 100644
--- a/buildtools/DEPS
+++ b/buildtools/DEPS
@@ -18,7 +18,7 @@
 
   # When changing these, also update the svn revisions in deps_revisions.gni
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
-  'libcxx_revision':       'fbddc46986100095d5f7ed1bc2bf795d3bb3e9e4',
+  'libcxx_revision':       '9b96c3dbd4e89c10d9fd8364da4b65f93c6f4276',
   'libcxxabi_revision':    '0d529660e32d77d9111912d73f2c74fc5fa2a858',
   'libunwind_revision':    '69d9b84cca8354117b9fe9705a4430d789ee599b',
 }
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index fd541ec..5b6c512 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # The libc++ svn revision that belongs to the git hash in DEPS. Used to cause
   # full rebuilds on libc++ rolls.
-  libcxx_svn_revision = "357619"
+  libcxx_svn_revision = "358423"
 }
diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index e4d6e3f..c1cf6f7 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -315,7 +315,7 @@
     raster_source->PlaybackToCanvas(
         canvas, options.content_size, options.full_raster_rect,
         options.playback_rect, raster_transform, settings);
-    surface->prepareForExternalIO();
+    surface->flush();
     EXPECT_EQ(gles2_context_provider_->ContextGL()->GetError(),
               static_cast<unsigned>(GL_NO_ERROR));
 
diff --git a/cc/tiles/gpu_image_decode_cache_perftest.cc b/cc/tiles/gpu_image_decode_cache_perftest.cc
index 5d0f2e6..f7d485e 100644
--- a/cc/tiles/gpu_image_decode_cache_perftest.cc
+++ b/cc/tiles/gpu_image_decode_cache_perftest.cc
@@ -151,7 +151,7 @@
       surface->getCanvas()->drawImageRect(decoded_image.image().get(),
                                           SkRect::MakeWH(1024, 2048),
                                           SkRect::MakeWH(614, 1229), &paint);
-      surface->prepareForExternalIO();
+      surface->flush();
     }
 
     cache_->DrawWithImageFinished(image, decoded_image);
diff --git a/cc/tiles/paint_worklet_image_cache.cc b/cc/tiles/paint_worklet_image_cache.cc
index 7d6cc848..639bf7d1 100644
--- a/cc/tiles/paint_worklet_image_cache.cc
+++ b/cc/tiles/paint_worklet_image_cache.cc
@@ -89,11 +89,10 @@
 std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>>
 PaintWorkletImageCache::GetPaintRecordAndRef(PaintWorkletInput* input) {
   base::AutoLock hold(records_lock_);
-  // If the |painter_| is null, then GetTaskForPaintWorkletImage will return a
-  // null TileTask, and hence there will be no cache entry for this input.
-  if (!painter_)
+  // If the |painter_| was null when GetTaskForPaintWorkletImage was called
+  // there will be no cache entry for this input.
+  if (records_.find(input) == records_.end())
     return std::make_pair(sk_make_sp<PaintOpBuffer>(), base::DoNothing::Once());
-  DCHECK(records_.find(input) != records_.end());
   records_[input].used_ref_count++;
   records_[input].num_of_frames_not_accessed = 0u;
   // The PaintWorkletImageCache object lives as long as the LayerTreeHostImpl,
diff --git a/cc/tiles/paint_worklet_image_cache_unittest.cc b/cc/tiles/paint_worklet_image_cache_unittest.cc
index 4a26f40..6ae3c0a 100644
--- a/cc/tiles/paint_worklet_image_cache_unittest.cc
+++ b/cc/tiles/paint_worklet_image_cache_unittest.cc
@@ -265,8 +265,14 @@
   EXPECT_EQ(task, nullptr);
 }
 
-TEST(PaintWorkletImageCacheTest, RecordAndCallbackAreEmptyWhenPainterIsNull) {
+TEST(PaintWorkletImageCacheTest,
+     RecordAndCallbackAreEmptyWhenInputWasntPainted) {
   TestPaintWorkletImageCache cache;
+  std::unique_ptr<TestPaintWorkletLayerPainter> painter =
+      std::make_unique<TestPaintWorkletLayerPainter>();
+  cache.SetPaintWorkletLayerPainter(std::move(painter));
+
+  // We request a record and callback without ever painting the input.
   PaintImage paint_image = CreatePaintImage(100, 100);
   std::pair<sk_sp<PaintRecord>, base::OnceCallback<void()>> result =
       cache.GetPaintRecordAndRef(paint_image.paint_worklet_input());
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index b3ed12a..396a871 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2060,7 +2060,7 @@
 
   if (_is_trichrome) {
     if (defined(invoker.is_64_bit_browser) && invoker.is_64_bit_browser) {
-      if (invoker.build_apk_secondary_abi && invoker.include_32_bit_webview) {
+      if (build_apk_secondary_abi && invoker.include_32_bit_webview) {
         _version_code = trichrome_64_32_version_code
       } else {
         _version_code = trichrome_64_version_code
@@ -2070,7 +2070,7 @@
     }
   } else {
     if (defined(invoker.is_64_bit_browser) && invoker.is_64_bit_browser) {
-      if (invoker.build_apk_secondary_abi && invoker.include_32_bit_webview) {
+      if (build_apk_secondary_abi && invoker.include_32_bit_webview) {
         _version_code = monochrome_64_32_version_code
       } else {
         _version_code = monochrome_64_version_code
@@ -2215,7 +2215,7 @@
     }
   }
 
-  monochrome_or_trichrome_public_bundle_tmpl("trichrome_64_chrome_bundle") {
+  monochrome_or_trichrome_public_bundle_tmpl("trichrome_chrome_64_bundle") {
     bundle_suffix = "64"
     is_64_bit_browser = true
     use_trichrome_library = true
@@ -2224,7 +2224,7 @@
     }
   }
 
-  monochrome_or_trichrome_public_bundle_tmpl("trichrome_64_32_chrome_bundle") {
+  monochrome_or_trichrome_public_bundle_tmpl("trichrome_chrome_64_32_bundle") {
     bundle_suffix = "6432"
     is_64_bit_browser = true
     use_trichrome_library = true
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 307c040..385b518 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1596,6 +1596,8 @@
   "java/src/org/chromium/chrome/browser/tabmodel/document/StorageDelegate.java",
   "java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java",
   "java/src/org/chromium/chrome/browser/tasks/ReturnToChromeExperimentsUtil.java",
+  "java/src/org/chromium/chrome/browser/tasks/EngagementTimeUtil.java",
+  "java/src/org/chromium/chrome/browser/tasks/JourneyManager.java",
   "java/src/org/chromium/chrome/browser/tasks/TasksUma.java",
   "java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_groups/LayoutTabGroupCreationButton.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 73ab714..c5c662a 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -179,6 +179,7 @@
   "junit/src/org/chromium/chrome/browser/tab/TabAttributesTest.java",
   "junit/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java",
   "junit/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java",
   "junit/src/org/chromium/chrome/browser/usage_stats/EventTrackerTest.java",
   "junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java",
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
index 79b5f6d..66fe798 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridContainerViewBinder.java
@@ -12,6 +12,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT;
 import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
 
+import android.support.v7.widget.GridLayoutManager;
 import android.widget.FrameLayout;
 
 import org.chromium.chrome.browser.util.ColorUtils;
@@ -42,7 +43,9 @@
         } else if (VISIBILITY_LISTENER == propertyKey) {
             view.setVisibilityListener(model.get(VISIBILITY_LISTENER));
         } else if (INITIAL_SCROLL_INDEX == propertyKey) {
-            view.scrollToPosition(model.get(INITIAL_SCROLL_INDEX));
+            // recyclerview.scrollToPosition() behaves incorrectly after cold start.
+            ((GridLayoutManager) view.getLayoutManager())
+                    .scrollToPositionWithOffset(model.get(INITIAL_SCROLL_INDEX), 0);
         } else if (TOP_CONTROLS_HEIGHT == propertyKey) {
             FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
             params.topMargin = model.get(TOP_CONTROLS_HEIGHT);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 0d6a74e..9fb74bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -144,6 +144,8 @@
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
+import org.chromium.chrome.browser.tasks.EngagementTimeUtil;
+import org.chromium.chrome.browser.tasks.JourneyManager;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.browser.toolbar.top.Toolbar;
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer;
@@ -1503,6 +1505,12 @@
         getCompositorViewHolder().setKeyboardExtensionView(
                 mManualFillingComponent.getKeyboardExtensionSizeManager());
 
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_ENGAGEMENT_REPORTING_ANDROID)) {
+            // The lifetime of this object is managed by the lifecycle dispatcher.
+            new JourneyManager(
+                    mTabModelSelector, getLifecycleDispatcher(), new EngagementTimeUtil());
+        }
+
         // Create after native initialization so subclasses that override this method have a chance
         // to setup.
         mPageViewTimer = createPageViewTimer();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
index 5a454a4..36cef232 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplication.java
@@ -20,6 +20,7 @@
 import org.chromium.base.CommandLineInitUtil;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.DiscardableReferencePool;
+import org.chromium.base.JNIUtils;
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.annotations.MainDex;
@@ -134,6 +135,7 @@
             }
         }
         AsyncTask.takeOverAndroidThreadPool();
+        JNIUtils.setClassLoader(getClassLoader());
     }
 
     private static Boolean shouldUseDebugFlags() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index e74fb21..b8c26ef 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -312,6 +312,7 @@
     public static final String SPANNABLE_INLINE_AUTOCOMPLETE = "SpannableInlineAutocomplete";
     public static final String SUBRESOURCE_FILTER = "SubresourceFilter";
     public static final String QUERY_IN_OMNIBOX = "QueryInOmnibox";
+    public static final String TAB_ENGAGEMENT_REPORTING_ANDROID = "TabEngagementReportingAndroid";
     public static final String TAB_GROUPS_ANDROID = "TabGroupsAndroid";
     public static final String TAB_GRID_LAYOUT_ANDROID = "TabGridLayoutAndroid";
     public static final String TAB_REPARENTING = "TabReparenting";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/EngagementTimeUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/EngagementTimeUtil.java
new file mode 100644
index 0000000..059ee9a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/EngagementTimeUtil.java
@@ -0,0 +1,28 @@
+// 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;
+
+/**
+ * Utility class to provide engagement time helper methods.
+ */
+public class EngagementTimeUtil {
+    /**
+     * Provide the current time in milliseconds.
+     *
+     * @return long - the current time in milliseconds.
+     */
+    public long currentTime() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * Given the last engagement timestamp, return the elapsed time in milliseconds since that time.
+     * @param lastEngagementMs - time of the last engagement
+     * @return time in milliseconds that have elapsed since lastEngagementMs
+     */
+    public long timeSinceLastEngagement(long lastEngagementMs) {
+        return currentTime() - lastEngagementMs;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/JourneyManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/JourneyManager.java
new file mode 100644
index 0000000..ed356f8
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/JourneyManager.java
@@ -0,0 +1,191 @@
+// 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;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.annotation.NonNull;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.AsyncTask;
+import org.chromium.chrome.browser.ChromeVersionInfo;
+import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.lifecycle.Destroyable;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
+import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+import org.chromium.content_public.browser.NavigationHandle;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages Journey related signals, specifically those related to tab engagement.
+ */
+public class JourneyManager implements Destroyable {
+    private static final long INVALID_START_TIME = -1;
+
+    @VisibleForTesting
+    static final String PREFS_FILE = "last_engagement_for_tab_id_pref";
+
+    @VisibleForTesting
+    static final String TAB_REVISIT_METRIC = "Tabs.TimeSinceLastView.OnTabView";
+
+    @VisibleForTesting
+    static final String TAB_CLOSE_METRIC = "Tabs.TimeSinceLastView.OnTabClose";
+
+    // We track this in seconds because UMA can only handle 32-bit signed integers, which 45 days
+    // will overflow.
+    private static final int MAX_ENGAGEMENT_TIME_S = (int) TimeUnit.DAYS.toSeconds(45);
+
+    private final TabModelSelectorTabObserver mTabModelSelectorTabObserver;
+    private final TabModelSelectorTabModelObserver mTabModelSelectorTabModelObserver;
+    private final ActivityLifecycleDispatcher mLifecycleDispatcher;
+    private final EngagementTimeUtil mEngagementTimeUtil;
+
+    private Map<Integer, Boolean> mDidFirstPaintPerTab = new HashMap<>();
+
+    public JourneyManager(TabModelSelector selector,
+            @NonNull ActivityLifecycleDispatcher dispatcher,
+            EngagementTimeUtil engagementTimeUtil) {
+        if (!ChromeVersionInfo.isLocalBuild() && !ChromeVersionInfo.isCanaryBuild()
+                && !ChromeVersionInfo.isDevBuild()) {
+            // We do not want this in beta/stable until it's no longer backed by SharedPreferences.
+            mTabModelSelectorTabObserver = null;
+            mTabModelSelectorTabModelObserver = null;
+            mLifecycleDispatcher = null;
+            mEngagementTimeUtil = null;
+            return;
+        }
+
+        mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(selector) {
+            @Override
+            public void onShown(Tab tab, @TabSelectionType int type) {
+                if (type != TabSelectionType.FROM_USER) return;
+
+                maybeRecordEngagementMetric(tab, TAB_REVISIT_METRIC);
+
+                handleTabEngagementStarted(tab);
+            }
+
+            @Override
+            public void onHidden(Tab tab, @Tab.TabHidingType int reason) {
+                handleTabEngagementStopped(tab);
+            }
+
+            @Override
+            public void onClosingStateChanged(Tab tab, boolean closing) {
+                if (!closing) return;
+
+                maybeRecordEngagementMetric(tab, TAB_CLOSE_METRIC);
+            }
+
+            @Override
+            public void onDidStartNavigation(Tab tab, NavigationHandle navigationHandle) {
+                if (!navigationHandle.isInMainFrame() || navigationHandle.isSameDocument()) return;
+
+                mDidFirstPaintPerTab.put(tab.getId(), false);
+            }
+
+            @Override
+            public void didFirstVisuallyNonEmptyPaint(Tab tab) {
+                mDidFirstPaintPerTab.put(tab.getId(), true);
+                handleTabEngagementStarted(tab);
+            }
+        };
+
+        mTabModelSelectorTabModelObserver = new TabModelSelectorTabModelObserver(selector) {
+            @Override
+            public void tabClosureCommitted(Tab tab) {
+                getPrefs().edit().remove(String.valueOf(tab.getId())).apply();
+            }
+        };
+
+        mLifecycleDispatcher = dispatcher;
+        mLifecycleDispatcher.register(this);
+
+        mEngagementTimeUtil = engagementTimeUtil;
+    }
+
+    @Override
+    public void destroy() {
+        mTabModelSelectorTabObserver.destroy();
+        mTabModelSelectorTabModelObserver.destroy();
+        mLifecycleDispatcher.unregister(this);
+    }
+
+    private void handleTabEngagementStarted(Tab tab) {
+        long lastEngagementMs = mEngagementTimeUtil.currentTime();
+
+        Boolean didFirstPaint = mDidFirstPaintPerTab.get(tab.getId());
+        if (didFirstPaint == null || !didFirstPaint) return;
+
+        storeLastEngagement(tab.getId(), lastEngagementMs);
+    }
+
+    private void handleTabEngagementStopped(Tab tab) {
+        long lastEngagementMs = mEngagementTimeUtil.currentTime();
+
+        Boolean didFirstPaint = mDidFirstPaintPerTab.get(tab.getId());
+        if (didFirstPaint == null || !didFirstPaint) {
+            return;
+        }
+
+        storeLastEngagement(tab.getId(), lastEngagementMs);
+    }
+
+    private void storeLastEngagement(int tabId, long lastEngagementTimestampMs) {
+        new AsyncTask<Void>() {
+            @Override
+            protected Void doInBackground() {
+                getPrefs().edit().putLong(String.valueOf(tabId), lastEngagementTimestampMs).apply();
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void result) {}
+        }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+    }
+
+    private SharedPreferences getPrefs() {
+        // TODO(mattsimmons): Add a native counterpart to this class and don't write directly to
+        //  shared prefs.
+        return ContextUtils.getApplicationContext().getSharedPreferences(
+                PREFS_FILE, Context.MODE_PRIVATE);
+    }
+
+    private long getLastEngagementTimestamp(Tab tab) {
+        return getPrefs().getLong(String.valueOf(tab.getId()), INVALID_START_TIME);
+    }
+
+    private void maybeRecordEngagementMetric(Tab tab, String name) {
+        long lastEngagement = getLastEngagementTimestamp(tab);
+
+        if (lastEngagement == INVALID_START_TIME) return;
+
+        // Compute elapsed time and convert to seconds.
+        long elapsedMs = mEngagementTimeUtil.timeSinceLastEngagement(lastEngagement);
+        int elapsedSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(elapsedMs);
+        RecordHistogram.recordCustomCountHistogram(
+                name, elapsedSeconds, 1, MAX_ENGAGEMENT_TIME_S, 50);
+    }
+
+    @VisibleForTesting
+    public TabObserver getTabModelSelectorTabObserver() {
+        return mTabModelSelectorTabObserver;
+    }
+
+    @VisibleForTesting
+    public TabModelObserver getTabModelSelectorTabModelObserver() {
+        return mTabModelSelectorTabModelObserver;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
index 134fb7f..b4e16f80 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
@@ -204,6 +204,13 @@
     }
 
     /**
+     * @return Whether the browser is currently in fullscreen mode.
+     */
+    private boolean isInFullscreenMode() {
+        return mFullscreenManager != null && mFullscreenManager.getPersistentFullscreenMode();
+    }
+
+    /**
      * The composited view is the composited version of the Android View. It is used to be able to
      * scroll the bottom controls off-screen synchronously. Since the bottom controls live below
      * the webcontents we re-size the webcontents through
@@ -211,7 +218,8 @@
      * visibility changes.
      */
     private void updateCompositedViewVisibility() {
-        final boolean isCompositedViewVisible = mIsBottomControlsVisible && !mIsKeyboardVisible;
+        final boolean isCompositedViewVisible =
+                mIsBottomControlsVisible && !mIsKeyboardVisible && !isInFullscreenMode();
         mModel.set(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE, isCompositedViewVisible);
         mFullscreenManager.setBottomControlsHeight(
                 isCompositedViewVisible ? mBottomControlsHeight : 0);
@@ -227,7 +235,8 @@
     private void updateAndroidViewVisibility() {
         mModel.set(BottomControlsProperties.ANDROID_VIEW_VISIBLE,
                 mIsBottomControlsVisible && !mIsKeyboardVisible && !mIsOverlayPanelShowing
-                        && !mIsInSwipeLayout && mFullscreenManager.getBottomControlOffset() == 0);
+                        && !mIsInSwipeLayout && mFullscreenManager.getBottomControlOffset() == 0
+                        && !isInFullscreenMode());
     }
 
     @Override
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java
new file mode 100644
index 0000000..c1070d8d
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/JourneyManagerTest.java
@@ -0,0 +1,270 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
+import org.chromium.base.task.test.BackgroundShadowAsyncTask;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tabmodel.TabList;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabSelectionType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for JourneyManager. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE,
+        shadows = {ShadowRecordHistogram.class, BackgroundShadowAsyncTask.class})
+public final class JourneyManagerTest {
+    private static final int LAST_ENGAGEMENT_ELAPSED_MS = 5000;
+    private static final int LAST_ENGAGEMENT_ELAPSED_S = 5;
+    private static final int TAB_ID = 123;
+    private static final long BASE_TIME_MS = 1000000L;
+
+    @Mock
+    private TabModel mTabModel;
+
+    @Mock
+    private TabModelSelector mTabModelSelector;
+
+    @Mock
+    private Tab mTab;
+
+    @Mock
+    private TabList mTabList;
+
+    @Mock
+    private ActivityLifecycleDispatcher mDispatcher;
+
+    @Mock
+    private EngagementTimeUtil mEngagementTimeUtil;
+
+    private JourneyManager mJourneyManager;
+
+    private TabObserver mTabModelSelectorTabObserver;
+
+    private TabModelObserver mTabModelSelectorTabModelObserver;
+
+    private SharedPreferences mSharedPreferences;
+
+    @Before
+    public void setUp() {
+        ShadowRecordHistogram.reset();
+        Robolectric.getBackgroundThreadScheduler().reset();
+
+        MockitoAnnotations.initMocks(this);
+
+        mSharedPreferences = ContextUtils.getApplicationContext().getSharedPreferences(
+                JourneyManager.PREFS_FILE, Context.MODE_PRIVATE);
+        mSharedPreferences.edit().clear().commit();
+
+        mJourneyManager = new JourneyManager(mTabModelSelector, mDispatcher, mEngagementTimeUtil);
+        mTabModelSelectorTabObserver = mJourneyManager.getTabModelSelectorTabObserver();
+        mTabModelSelectorTabModelObserver = mJourneyManager.getTabModelSelectorTabModelObserver();
+
+        verify(mDispatcher).register(mJourneyManager);
+
+        // Set up a tab.
+        doReturn(TAB_ID).when(mTab).getId();
+
+        // Set up tab model, returning tab above as current.
+        List<TabModel> tabModels = new ArrayList<>();
+        tabModels.add(mTabModel);
+        doReturn(tabModels).when(mTabModelSelector).getModels();
+        doReturn(mTab).when(mTabModelSelector).getCurrentTab();
+        doReturn(mTabList).when(mTabModel).getComprehensiveModel();
+        doReturn(0).when(mTabList).getCount();
+
+        doReturn(BASE_TIME_MS).when(mEngagementTimeUtil).currentTime();
+    }
+
+    @Test
+    public void onTabShown_noPreviousEngagement() {
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_USER);
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_REVISIT_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+    }
+
+    @Test
+    public void onTabShown_previousEngagementExists() {
+        // Paint to set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Move time forward.
+        doReturn((long) LAST_ENGAGEMENT_ELAPSED_MS)
+                .when(mEngagementTimeUtil)
+                .timeSinceLastEngagement(anyLong());
+
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_USER);
+
+        assertEquals(1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_REVISIT_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+    }
+
+    @Test
+    public void onTabShown_previousEngagementExists_contentNotYetPainted() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_USER);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn(BASE_TIME_MS + LAST_ENGAGEMENT_ELAPSED_MS).when(mEngagementTimeUtil).currentTime();
+
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_USER);
+
+        assertEquals(-1, mSharedPreferences.getLong(String.valueOf(mTab.getId()), -1));
+    }
+
+    @Test
+    public void onTabShown_previousEngagementExists_notSelectedByUser() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn((long) LAST_ENGAGEMENT_ELAPSED_MS)
+                .when(mEngagementTimeUtil)
+                .timeSinceLastEngagement(anyLong());
+
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_EXIT);
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_REVISIT_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+    }
+
+    @Test
+    public void onTabHidden_shouldSaveLastEngagement() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn(BASE_TIME_MS + LAST_ENGAGEMENT_ELAPSED_MS).when(mEngagementTimeUtil).currentTime();
+
+        mTabModelSelectorTabObserver.onHidden(mTab, Tab.TabHidingType.ACTIVITY_HIDDEN);
+        flushAsyncPrefs();
+
+        assertEquals(BASE_TIME_MS + LAST_ENGAGEMENT_ELAPSED_MS,
+                mSharedPreferences.getLong(String.valueOf(mTab.getId()), -1));
+    }
+
+    @Test
+    public void onClosingStateChanged_noPreviousEngagement() {
+        mTabModelSelectorTabObserver.onShown(mTab, TabSelectionType.FROM_USER);
+        flushAsyncPrefs();
+
+        mTabModelSelectorTabObserver.onClosingStateChanged(mTab, true);
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_CLOSE_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+    }
+
+    @Test
+    public void onClosingStateChanged_previousEngagementExists_tabClosureNotCommitted() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn((long) LAST_ENGAGEMENT_ELAPSED_MS)
+                .when(mEngagementTimeUtil)
+                .timeSinceLastEngagement(anyLong());
+
+        mTabModelSelectorTabObserver.onClosingStateChanged(mTab, true);
+
+        assertEquals(1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_CLOSE_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+
+        assertTrue(mSharedPreferences.contains(String.valueOf(mTab.getId())));
+    }
+
+    @Test
+    public void onClosingStateChanged_previousEngagementExists_notClosing() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn((long) LAST_ENGAGEMENT_ELAPSED_MS)
+                .when(mEngagementTimeUtil)
+                .timeSinceLastEngagement(anyLong());
+
+        mTabModelSelectorTabObserver.onClosingStateChanged(mTab, false);
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_CLOSE_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+    }
+
+    @Test
+    public void onClosingStateChanged_previousEngagementExists_tabClosureCommitted() {
+        // Set did paint flag.
+        mTabModelSelectorTabObserver.didFirstVisuallyNonEmptyPaint(mTab);
+        flushAsyncPrefs();
+
+        // Advance time.
+        doReturn((long) LAST_ENGAGEMENT_ELAPSED_MS)
+                .when(mEngagementTimeUtil)
+                .timeSinceLastEngagement(anyLong());
+
+        mTabModelSelectorTabObserver.onClosingStateChanged(mTab, true);
+
+        assertEquals(1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        JourneyManager.TAB_CLOSE_METRIC, LAST_ENGAGEMENT_ELAPSED_S));
+
+        mTabModelSelectorTabModelObserver.tabClosureCommitted(mTab);
+        flushAsyncPrefs();
+
+        assertFalse(mSharedPreferences.contains(String.valueOf(mTab.getId())));
+    }
+
+    @Test
+    public void destroy_unregistersLifecycleObserver() {
+        mJourneyManager.destroy();
+        verify(mDispatcher).unregister(mJourneyManager);
+    }
+
+    private void flushAsyncPrefs() {
+        try {
+            BackgroundShadowAsyncTask.runBackgroundTasks();
+        } catch (Exception ex) {
+        } finally {
+            mSharedPreferences.edit().commit();
+        }
+    }
+}
diff --git a/chrome/android/touchless/java/res/layout/dialog_list_item.xml b/chrome/android/touchless/java/res/layout/dialog_list_item.xml
index 2db6530..91f25a5 100644
--- a/chrome/android/touchless/java/res/layout/dialog_list_item.xml
+++ b/chrome/android/touchless/java/res/layout/dialog_list_item.xml
@@ -6,6 +6,10 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:paddingStart="12dp"
+    android:paddingEnd="12dp"
+    android:paddingTop="9dp"
+    android:paddingBottom="9dp"
     android:orientation="horizontal"
     android:background="@color/modern_primary_color">
 
@@ -14,17 +18,14 @@
         android:layout_width="18dp"
         android:layout_height="18dp"
         android:layout_gravity="center_vertical"
-        android:layout_marginBottom="9dp"
-        android:layout_marginStart="12dp"
-        android:layout_marginTop="9dp"
-        android:padding="3dp"
-        android:scaleType="centerInside"/>
+        android:layout_marginEnd="10dp"
+        android:scaleType="centerInside"
+        android:visibility="gone"/>
 
     <TextView
         android:id="@+id/dialog_item_text"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginStart="10dp"
+        android:layout_height="wrap_content"
         android:gravity="center_vertical"
         android:textAppearance="@style/TextAppearance.BlackBody"/>
 
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
index 8898657..ff19bc3 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
@@ -153,6 +153,7 @@
         ChromeImageView imageView = view.findViewById(R.id.dialog_item_icon);
         TextView textView = view.findViewById(R.id.dialog_item_text);
         if (DialogListItemProperties.ICON == propertyKey) {
+            imageView.setVisibility(View.VISIBLE);
             Drawable icon = model.get(DialogListItemProperties.ICON).mutate();
             icon.clearColorFilter();
             imageView.setImageDrawable(icon);
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index e4f0c7c..345f6c1 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -110,6 +110,7 @@
 #include "base/system/sys_info.h"
 #include "chrome/browser/chromeos/boot_times_recorder.h"
 #include "chrome/browser/chromeos/dbus/dbus_helper.h"
+#include "chrome/browser/chromeos/startup_settings_cache.h"
 #include "chromeos/constants/chromeos_paths.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/hugepage_text/hugepage_text.h"
@@ -899,8 +900,14 @@
     // via the preference prefs::kApplicationLocale. The browser process uses
     // the --lang flag to pass the value of the PrefService in here. Maybe
     // this value could be passed in a different way.
-    const std::string locale =
-        command_line.GetSwitchValueASCII(switches::kLang);
+    std::string locale = command_line.GetSwitchValueASCII(switches::kLang);
+#if defined(OS_CHROMEOS)
+    if (process_type == service_manager::switches::kZygoteProcess) {
+      DCHECK(locale.empty());
+      // See comment at ReadAppLocale() for why we do this.
+      locale = chromeos::startup_settings_cache::ReadAppLocale();
+    }
+#endif
 #if defined(OS_ANDROID)
     // The renderer sandbox prevents us from accessing our .pak files directly.
     // Therefore file descriptors to the .pak files that we need are passed in
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 818af247..493ee87 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -869,22 +869,6 @@
   <message name="IDS_LOGIN_PASSWORD_CHANGED_TRY_AGAIN" desc="Label for the retry button on the proceed anyway step in the the GAIA password changed flow">
     Try again
   </message>
-  <message name="IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_TITLE" desc="Title for the fatal cryptohome error dialog box">
-    Can't sign in
-  </message>
-  <message name="IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_MESSAGE" desc="Message for the fatal cryptohome error dialog box">
-    We're sorry. We can't access your profile. Files and data stored on this device may have been lost.<ph name="BR">&lt;br&gt;</ph>
-    <ph name="BR">&lt;br&gt;</ph>
-    You'll have to set up your profile again.<ph name="BR">&lt;br&gt;</ph>
-    <ph name="BR">&lt;br&gt;</ph>
-    On the next screen, please send feedback to help us fix the issue.
-  </message>
-  <message name="IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_CONTINUE" desc="Label of the button to continue with re-creating cryptohome for the fatal cryptohome error dialog box">
-    Continue
-  </message>
-  <message name="IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_WAIT_MESSAGE" desc="Message to show when the fatal cryptohome error dialog box is waiting for user profile re-creation to be closed.">
-    Re-creating profile, please wait...
-  </message>
   <message name="IDS_LOGIN_SAML_NOTICE" desc="Text message displayed above SAML portal to early indicate that the user is being redirected to another sign-in provider. This is the version of the string used in the GAIA flow.">
     This sign-in service is hosted by <ph name="SAML_DOMAIN">$1<ex>saml.com</ex></ph>
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index aa11e3d..c3ff09b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1062,6 +1062,8 @@
     "performance_manager/chrome_browser_main_extra_parts_performance_manager.h",
     "performance_manager/chrome_content_browser_client_performance_manager_part.cc",
     "performance_manager/chrome_content_browser_client_performance_manager_part.h",
+    "performance_manager/decorators/frozen_frame_aggregator.cc",
+    "performance_manager/decorators/frozen_frame_aggregator.h",
     "performance_manager/decorators/page_almost_idle_decorator.cc",
     "performance_manager/decorators/page_almost_idle_decorator.h",
     "performance_manager/graph/frame_node_impl.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f2f39dc..74bef82 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -981,6 +981,18 @@
 };
 #endif  // OS_ANDROID
 
+const FeatureEntry::FeatureParam kVizHitTestDrawQuadEnabled[] = {
+    {"provider", "draw_quad"}};
+
+const FeatureEntry::FeatureParam kVizHitTestSurfaceLayerEnabled[] = {
+    {"provider", "surface_layer"}};
+
+const FeatureEntry::FeatureVariation kVizHitTestVariations[] = {
+    {"DrawQuad", kVizHitTestDrawQuadEnabled,
+     base::size(kVizHitTestDrawQuadEnabled), nullptr},
+    {"SurfaceLayer", kVizHitTestSurfaceLayerEnabled,
+     base::size(kVizHitTestSurfaceLayerEnabled), nullptr}};
+
 // RECORDING USER METRICS FOR FLAGS:
 // -----------------------------------------------------------------------------
 // The first line of the entry is the internal name.
@@ -3076,10 +3088,11 @@
      flag_descriptions::kQueryInOmniboxDescription, kOsAll,
      FEATURE_VALUE_TYPE(omnibox::kQueryInOmnibox)},
 
-    {"enable-viz-hit-test-draw-quad",
-     flag_descriptions::kVizHitTestDrawQuadName,
-     flag_descriptions::kVizHitTestDrawQuadDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kEnableVizHitTestDrawQuad)},
+    {"enable-viz-hit-test", flag_descriptions::kVizHitTestName,
+     flag_descriptions::kVizHitTestDescription, kOsAll,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(features::kEnableVizHitTest,
+                                    kVizHitTestVariations,
+                                    "VizHitTestDataSource")},
 
 #if BUILDFLAG(ENABLE_PDF)
 #if defined(OS_CHROMEOS)
@@ -3149,16 +3162,19 @@
     {"enable-tab-groups", flag_descriptions::kTabGroupsAndroidName,
      flag_descriptions::kTabGroupsAndroidDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kTabGroupsAndroid)},
-#endif  // OS_ANDROID
 
-#if defined(OS_ANDROID)
     {"enable-tab-switcher-on-return",
      flag_descriptions::kTabSwitcherOnReturnName,
      flag_descriptions::kTabSwitcherOnReturnDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kTabSwitcherOnReturn,
                                     kTabSwitcherOnReturnVariations,
                                     "TabSwitcherOnReturn")},
-#endif
+
+    {"enable-tab-engagement-reporting",
+     flag_descriptions::kTabEngagementReportingName,
+     flag_descriptions::kTabEngagementReportingDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kTabEngagementReportingAndroid)},
+#endif  // OS_ANDROID
 
     {"enable-built-in-module-all", flag_descriptions::kBuiltInModuleAllName,
      flag_descriptions::kBuiltInModuleAllDescription, kOsAll,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 33f548d..5c5c183 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -168,6 +168,7 @@
     &kSpannableInlineAutocomplete,
     &kSpecialLocaleWrapper,
     &kSpecialUserDecision,
+    &kTabEngagementReportingAndroid,
     &kTabGroupsAndroid,
     &kTabGridLayoutAndroid,
     &kTabReparenting,
@@ -493,6 +494,9 @@
 const base::Feature kSpecialUserDecision{"SpecialUserDecision",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kTabEngagementReportingAndroid{
+    "TabEngagementReportingAndroid", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kTabGroupsAndroid{"TabGroupsAndroid",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index ec2067f1..0264acb 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -99,6 +99,7 @@
 extern const base::Feature kSpannableInlineAutocomplete;
 extern const base::Feature kSpecialLocaleWrapper;
 extern const base::Feature kSpecialUserDecision;
+extern const base::Feature kTabEngagementReportingAndroid;
 extern const base::Feature kTabGroupsAndroid;
 extern const base::Feature kTabGridLayoutAndroid;
 extern const base::Feature kTabReparenting;
diff --git a/chrome/browser/android/vr/arcore_device/ar_image_transport.cc b/chrome/browser/android/vr/arcore_device/ar_image_transport.cc
index 7e552c9..53f6538f 100644
--- a/chrome/browser/android/vr/arcore_device/ar_image_transport.cc
+++ b/chrome/browser/android/vr/arcore_device/ar_image_transport.cc
@@ -9,6 +9,7 @@
 #include "base/containers/queue.h"
 #include "base/trace_event/traced_value.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
 #include "ui/gfx/gpu_fence.h"
 #include "ui/gl/gl_bindings.h"
@@ -40,9 +41,7 @@
       shared_gpu_memory_buffer;
 
   // Resources in the remote GPU process command buffer context
-  std::unique_ptr<gpu::MailboxHolder> mailbox_holder;
-  GLuint remote_texture_id = 0;
-  GLuint remote_image_id = 0;
+  gpu::MailboxHolder mailbox_holder;
 
   // Resources in the local GL context
   GLuint local_texture_id = 0;
@@ -66,7 +65,21 @@
       mailbox_bridge_(std::move(mailbox_bridge)),
       swap_chain_(std::make_unique<SharedFrameBufferSwapChain>()) {}
 
-ArImageTransport::~ArImageTransport() {}
+ArImageTransport::~ArImageTransport() {
+  DCHECK(IsOnGlThread());
+  while (!swap_chain_->buffers.empty()) {
+    std::unique_ptr<SharedFrameBuffer> buffer =
+        std::move(swap_chain_->buffers.front());
+    swap_chain_->buffers.pop();
+    if (!buffer->mailbox_holder.mailbox.IsZero()) {
+      DVLOG(2) << ": DestroySharedImage, mailbox="
+               << buffer->mailbox_holder.mailbox.ToDebugString();
+      // Note: the sync token in mailbox_holder may not be accurate. See comment
+      // in TransferFrame below.
+      mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
+    }
+  }
+}
 
 bool ArImageTransport::Initialize() {
   DCHECK(IsOnGlThread());
@@ -96,12 +109,12 @@
 
   TRACE_EVENT0("gpu", __FUNCTION__);
   // Unbind previous image (if any).
-  if (buffer->remote_image_id) {
-    DVLOG(2) << ": UnbindSharedBuffer, remote_image="
-             << buffer->remote_image_id;
-    mailbox_bridge_->UnbindSharedBuffer(buffer->remote_image_id,
-                                        buffer->remote_texture_id);
-    buffer->remote_image_id = 0;
+  if (!buffer->mailbox_holder.mailbox.IsZero()) {
+    DVLOG(2) << ": DestroySharedImage, mailbox="
+             << buffer->mailbox_holder.mailbox.ToDebugString();
+    // Note: the sync token in mailbox_holder may not be accurate. See comment
+    // in TransferFrame below.
+    mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
   }
 
   DVLOG(2) << __FUNCTION__ << ": width=" << size.width()
@@ -118,11 +131,14 @@
           kBufferId, size, format, usage,
           gpu::GpuMemoryBufferImpl::DestructionCallback());
 
-  buffer->remote_image_id = mailbox_bridge_->BindSharedBufferImage(
-      buffer->shared_gpu_memory_buffer.get(), size, format, usage,
-      buffer->remote_texture_id);
-  DVLOG(2) << ": BindSharedBufferImage, remote_image="
-           << buffer->remote_image_id;
+  uint32_t shared_image_usage = gpu::SHARED_IMAGE_USAGE_SCANOUT |
+                                gpu::SHARED_IMAGE_USAGE_DISPLAY |
+                                gpu::SHARED_IMAGE_USAGE_GLES2;
+  buffer->mailbox_holder =
+      mailbox_bridge_->CreateSharedImage(buffer->shared_gpu_memory_buffer.get(),
+                                         gfx::ColorSpace(), shared_image_usage);
+  DVLOG(2) << ": CreateSharedImage, mailbox="
+           << buffer->mailbox_holder.mailbox.ToDebugString();
 
   auto img = base::MakeRefCounted<gl::GLImageAHardwareBuffer>(size);
 
@@ -151,11 +167,6 @@
   for (int i = 0; i < kSharedBufferSwapChainSize; ++i) {
     std::unique_ptr<SharedFrameBuffer> buffer =
         std::make_unique<SharedFrameBuffer>();
-    // Remote resources
-    buffer->mailbox_holder = std::make_unique<gpu::MailboxHolder>();
-    buffer->mailbox_holder->texture_target = GL_TEXTURE_2D;
-    buffer->remote_texture_id =
-        mailbox_bridge_->CreateMailboxTexture(&buffer->mailbox_holder->mailbox);
 
     // Local resources
     glGenTextures(1, &buffer->local_texture_id);
@@ -173,6 +184,9 @@
   DCHECK(IsOnGlThread());
   // TODO(klausw): find out when a buffer is actually done being used
   // including by GL so we can know if we are overwriting one.
+  // A sync token needs to be returned by the client and stashed into
+  // shared_buffer->mailbox_holder.sync_token, then waited upon before reusing
+  // the buffer.
   DCHECK(swap_chain_->buffers.size() > 0);
 
   glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, transfer_fbo_);
@@ -213,10 +227,14 @@
   // Make a GpuFence and place it in the GPU stream for sequencing.
   std::unique_ptr<gl::GLFence> gl_fence = gl::GLFence::CreateForGpuFence();
   std::unique_ptr<gfx::GpuFence> gpu_fence = gl_fence->GetGpuFence();
-  mailbox_bridge_->WaitForClientGpuFence(gpu_fence.get());
-  mailbox_bridge_->GenSyncToken(&shared_buffer->mailbox_holder->sync_token);
 
-  gpu::MailboxHolder rendered_frame_holder = *shared_buffer->mailbox_holder;
+  // Have GL wait on both the client fence and the creation/return sync token.
+  // TODO(piman): this should probably do an UpdateSharedImage.
+  mailbox_bridge_->WaitSyncToken(shared_buffer->mailbox_holder.sync_token);
+  mailbox_bridge_->WaitForClientGpuFence(gpu_fence.get());
+  mailbox_bridge_->GenSyncToken(&shared_buffer->mailbox_holder.sync_token);
+
+  gpu::MailboxHolder rendered_frame_holder = shared_buffer->mailbox_holder;
 
   // Done with the shared buffer.
   swap_chain_->buffers.push(std::move(shared_buffer));
diff --git a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
index fe94ede..84cb67b 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_device_unittest.cc
@@ -73,10 +73,6 @@
 
   void BindContextProviderToCurrentThread() override {}
 
-  uint32_t CreateMailboxTexture(gpu::Mailbox* mailbox) override {
-    return TEXTURE_ID;
-  }
-
   bool IsConnected() override { return true; }
 
   void CallCallback() { std::move(callback_).Run(); }
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index 73cc212..e271b0e 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -101,7 +101,10 @@
       environment_binding_(this),
       weak_ptr_factory_(this) {}
 
-ArCoreGl::~ArCoreGl() {}
+ArCoreGl::~ArCoreGl() {
+  DCHECK(IsOnGlThread());
+  ar_image_transport_.reset();
+}
 
 void ArCoreGl::Initialize(vr::ArCoreInstallUtils* install_utils,
                           ArCoreFactory* arcore_factory,
diff --git a/chrome/browser/android/vr/gvr_scheduler_delegate.cc b/chrome/browser/android/vr/gvr_scheduler_delegate.cc
index 531f6cc..309d4613 100644
--- a/chrome/browser/android/vr/gvr_scheduler_delegate.cc
+++ b/chrome/browser/android/vr/gvr_scheduler_delegate.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/vr/scheduler_ui_interface.h"
 #include "content/public/common/content_features.h"
 #include "device/vr/android/gvr/gvr_delegate.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/config/gpu_driver_bug_workaround_type.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
@@ -91,6 +92,18 @@
 GvrSchedulerDelegate::~GvrSchedulerDelegate() {
   ClosePresentationBindings();
   webxr_.EndPresentation();
+  if (webxr_use_shared_buffer_draw_) {
+    std::vector<std::unique_ptr<WebXrSharedBuffer>> buffers =
+        webxr_.TakeSharedBuffers();
+    for (auto& buffer : buffers) {
+      if (!buffer->mailbox_holder.mailbox.IsZero()) {
+        DCHECK(mailbox_bridge_);
+        DVLOG(2) << ": DestroySharedImage, mailbox="
+                 << buffer->mailbox_holder.mailbox.ToDebugString();
+        mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
+      }
+    }
+  }
 }
 
 void GvrSchedulerDelegate::SetBrowserRenderer(
@@ -863,7 +876,8 @@
     CHECK(webxr_.HaveAnimatingFrame());
     WebXrSharedBuffer* buffer = webxr_.GetAnimatingFrame()->shared_buffer.get();
     DCHECK(buffer);
-    frame_data->buffer_holder = *buffer->mailbox_holder;
+    DCHECK(buffer->mailbox_holder.sync_token.verified_flush());
+    frame_data->buffer_holder = buffer->mailbox_holder;
   }
 
   int64_t prediction_nanos = GetPredictedFrameTime().InMicroseconds() * 1000;
@@ -914,12 +928,6 @@
         std::make_unique<WebXrSharedBuffer>();
     buffer = webxr_.GetAnimatingFrame()->shared_buffer.get();
 
-    // Remote resources
-    buffer->mailbox_holder = std::make_unique<gpu::MailboxHolder>();
-    buffer->mailbox_holder->texture_target = GL_TEXTURE_2D;
-    buffer->remote_texture =
-        mailbox_bridge_->CreateMailboxTexture(&buffer->mailbox_holder->mailbox);
-
     // Local resources
     glGenTextures(1, &buffer->local_texture);
   }
@@ -927,12 +935,6 @@
   if (webxr_surface_size != buffer->size) {
     // Don't need the image for zero copy mode.
     WebXrCreateOrResizeSharedBufferImage(buffer, webxr_surface_size);
-    // We always need a valid sync token, even if not using
-    // the image. The Renderer waits for it before using the
-    // mailbox. Technically we don't need to update it
-    // after resize for zero copy mode, but we do need it
-    // after initial creation.
-    mailbox_bridge_->GenSyncToken(&buffer->mailbox_holder->sync_token);
 
     // Save the size to avoid expensive reallocation next time.
     buffer->size = webxr_surface_size;
@@ -944,11 +946,10 @@
     const gfx::Size& size) {
   TRACE_EVENT0("gpu", __func__);
   // Unbind previous image (if any).
-  if (buffer->remote_image) {
-    DVLOG(2) << ": UnbindSharedBuffer, remote_image=" << buffer->remote_image;
-    mailbox_bridge_->UnbindSharedBuffer(buffer->remote_image,
-                                        buffer->remote_texture);
-    buffer->remote_image = 0;
+  if (!buffer->mailbox_holder.mailbox.IsZero()) {
+    DVLOG(2) << ": DestroySharedImage, mailbox="
+             << buffer->mailbox_holder.mailbox.ToDebugString();
+    mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
   }
 
   DVLOG(2) << __func__ << ": width=" << size.width()
@@ -964,9 +965,13 @@
       kBufferId, size, format, usage,
       gpu::GpuMemoryBufferImpl::DestructionCallback());
 
-  buffer->remote_image = mailbox_bridge_->BindSharedBufferImage(
-      buffer->gmb.get(), size, format, usage, buffer->remote_texture);
-  DVLOG(2) << ": BindSharedBufferImage, remote_image=" << buffer->remote_image;
+  uint32_t shared_image_usage = gpu::SHARED_IMAGE_USAGE_SCANOUT |
+                                gpu::SHARED_IMAGE_USAGE_DISPLAY |
+                                gpu::SHARED_IMAGE_USAGE_GLES2;
+  buffer->mailbox_holder = mailbox_bridge_->CreateSharedImage(
+      buffer->gmb.get(), gfx::ColorSpace(), shared_image_usage);
+  DVLOG(2) << ": CreateSharedImage, mailbox="
+           << buffer->mailbox_holder.mailbox.ToDebugString();
 
   scoped_refptr<gl::GLImageAHardwareBuffer> img(
       new gl::GLImageAHardwareBuffer(graphics_->webxr_surface_size()));
@@ -1043,11 +1048,21 @@
   if (!IsSubmitFrameExpected(frame_index))
     return;
 
-  // Renderer didn't submit a frame. Wait for the sync token to ensure
-  // that any mailbox_bridge_ operations for the next frame happen after
-  // whatever drawing the Renderer may have done before exiting.
-  if (webxr_.mailbox_bridge_ready())
-    mailbox_bridge_->WaitSyncToken(sync_token);
+  if (webxr_use_shared_buffer_draw_) {
+    // Renderer didn't submit a frame. Stash the sync token in the mailbox
+    // holder, so that we use the dependency before destroying or recycling the
+    // shared image.
+    WebXrSharedBuffer* buffer = webxr_.GetAnimatingFrame()->shared_buffer.get();
+    DCHECK(buffer);
+    DCHECK(sync_token.verified_flush());
+    buffer->mailbox_holder.sync_token = sync_token;
+  } else {
+    // Renderer didn't submit a frame. Wait for the sync token to ensure
+    // that any mailbox_bridge_ operations for the next frame happen after
+    // whatever drawing the Renderer may have done before exiting.
+    if (webxr_.mailbox_bridge_ready())
+      mailbox_bridge_->WaitSyncToken(sync_token);
+  }
 
   DVLOG(2) << __func__ << ": recycle unused animating frame";
   DCHECK(webxr_.HaveAnimatingFrame());
@@ -1078,6 +1093,22 @@
   if (!SubmitFrameCommon(frame_index, time_waited))
     return;
 
+  if (webxr_use_shared_buffer_draw_) {
+    // Renderer submitted a frame. Stash the sync token in the mailbox
+    // holder, so that we use the dependency before destroying or recycling the
+    // shared image.
+    WebXrSharedBuffer* buffer = webxr_.GetAnimatingFrame()->shared_buffer.get();
+    DCHECK(buffer);
+    DCHECK(sync_token.verified_flush());
+    buffer->mailbox_holder.sync_token = sync_token;
+  } else {
+    mojo::ReportBadMessage(
+        "SubmitFrameDrawnIntoTexture called while using the wrong transport "
+        "mode");
+    ClosePresentationBindings();
+    return;
+  }
+
   webxr_.ProcessOrDefer(
       base::BindOnce(&GvrSchedulerDelegate::ProcessWebVrFrameFromGMB,
                      weak_ptr_factory_.GetWeakPtr(), frame_index, sync_token));
diff --git a/chrome/browser/android/vr/mailbox_to_surface_bridge.cc b/chrome/browser/android/vr/mailbox_to_surface_bridge.cc
index 9551e24..57e22f0 100644
--- a/chrome/browser/android/vr/mailbox_to_surface_bridge.cc
+++ b/chrome/browser/android/vr/mailbox_to_surface_bridge.cc
@@ -19,6 +19,8 @@
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/client/shared_image_interface.h"
+#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/mailbox_holder.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
@@ -355,46 +357,34 @@
   gl_->DestroyGpuFenceCHROMIUM(id);
 }
 
-uint32_t MailboxToSurfaceBridge::CreateMailboxTexture(gpu::Mailbox* mailbox) {
+gpu::MailboxHolder MailboxToSurfaceBridge::CreateSharedImage(
+    gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
+    const gfx::ColorSpace& color_space,
+    uint32_t usage) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
 
-  GLuint tex = 0;
-  gl_->GenTextures(1, &tex);
-  gl_->BindTexture(GL_TEXTURE_2D, tex);
-  gl_->ProduceTextureDirectCHROMIUM(tex, mailbox->name);
+  auto* sii = context_provider_->SharedImageInterface();
+  DCHECK(sii);
 
-  return tex;
+  gpu::MailboxHolder mailbox_holder;
+  mailbox_holder.mailbox =
+      sii->CreateSharedImage(buffer, nullptr, color_space, usage);
+  mailbox_holder.sync_token = sii->GenVerifiedSyncToken();
+  DCHECK(!gpu::NativeBufferNeedsPlatformSpecificTextureTarget(
+      buffer->GetFormat()));
+  mailbox_holder.texture_target = GL_TEXTURE_2D;
+  return mailbox_holder;
 }
 
-uint32_t MailboxToSurfaceBridge::BindSharedBufferImage(
-    gfx::GpuMemoryBuffer* buffer,
-    const gfx::Size& size,
-    gfx::BufferFormat format,
-    gfx::BufferUsage usage,
-    uint32_t texture_id) {
+void MailboxToSurfaceBridge::DestroySharedImage(
+    const gpu::MailboxHolder& mailbox_holder) {
   TRACE_EVENT0("gpu", __FUNCTION__);
   DCHECK(IsConnected());
 
-  auto img = gl_->CreateImageCHROMIUM(buffer->AsClientBuffer(), size.width(),
-                                      size.height(), GL_RGBA);
-
-  gl_->BindTexture(GL_TEXTURE_2D, texture_id);
-  gl_->BindTexImage2DCHROMIUM(GL_TEXTURE_2D, img);
-  gl_->BindTexture(GL_TEXTURE_2D, 0);
-
-  return img;
-}
-
-void MailboxToSurfaceBridge::UnbindSharedBuffer(GLuint image_id,
-                                                GLuint texture_id) {
-  TRACE_EVENT0("gpu", __FUNCTION__);
-  DCHECK(IsConnected());
-
-  gl_->BindTexture(GL_TEXTURE_2D, texture_id);
-  gl_->ReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id);
-  gl_->BindTexture(GL_TEXTURE_2D, 0);
-  gl_->DestroyImageCHROMIUM(image_id);
+  auto* sii = context_provider_->SharedImageInterface();
+  DCHECK(sii);
+  sii->DestroySharedImage(mailbox_holder.sync_token, mailbox_holder.mailbox);
 }
 
 void MailboxToSurfaceBridge::DestroyContext() {
diff --git a/chrome/browser/android/vr/mailbox_to_surface_bridge.h b/chrome/browser/android/vr/mailbox_to_surface_bridge.h
index 1103bc6..86f7365 100644
--- a/chrome/browser/android/vr/mailbox_to_surface_bridge.h
+++ b/chrome/browser/android/vr/mailbox_to_surface_bridge.h
@@ -20,12 +20,12 @@
 }  // namespace gl
 
 namespace gfx {
-class GpuMemoryBuffer;
+class ColorSpace;
 }
 
 namespace gpu {
 class ContextSupport;
-struct Mailbox;
+class GpuMemoryBufferImplAndroidHardwareBuffer;
 struct MailboxHolder;
 struct SyncToken;
 namespace gles2 {
@@ -58,13 +58,13 @@
   //
   // To use entirely on the GL thread:
   // Call CreateAndBindContextProvider(callback) from your thread.
-  // When the callback is invoked, the object is ready for GL calls
-  // such as CreateMailboxTexture().
+  // When the callback is invoked, the object is ready for calls that use the
+  // context, such as CreateSharedImage().
   //
   // To create on one thread and use GL on another:
   // Call CreateUnboundContextProvider(callback) and then make sure
   // to call BindContextProviderToCurrentThread() from your GL
-  // thread afterwards before making an GL-related calls.
+  // thread afterwards before making a context-related calls.
 
   // Asynchronously create the context using the surface provided by an earlier
   // CreateSurface call, or an offscreen context if that wasn't called. Also
@@ -77,9 +77,9 @@
   // wasn't run on the GL thread. The provided callback is run on the
   // constructor thread. After that, you can pass the MailboxToSurfaceBridge
   // to another thread. You must call BindContextProviderToCurrentThread()
-  // on the target GL thread before using any GL methods.
-  // The GL methods check that they are called on this thread, so there
-  // will be a DCHECK error if they are not used consistently.
+  // on the target GL thread before using any context-related methods.
+  // The context-related methods check that they are called on this thread, so
+  // there will be a DCHECK error if they are not used consistently.
   virtual void CreateUnboundContextProvider(base::OnceClosure callback);
 
   // Client must call this on the target (GL) thread after
@@ -112,21 +112,19 @@
       const gpu::SyncToken& sync_token,
       base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback);
 
-  // Creates a texture and binds it to a newly created mailbox. Returns its
-  // mailbox and texture ID in the command buffer context. (Don't use that
-  // in the local GL context, it's not valid there.)
-  virtual uint32_t CreateMailboxTexture(gpu::Mailbox* mailbox);
-
-  // Creates a GLImage from the |buffer| and binds it to the supplied texture_id
-  // in the GPU process. Returns the image ID in the command buffer context.
+  // Creates a shared image bound to |buffer|. Returns a mailbox holder that
+  // references the shared image with a sync token representing a point after
+  // the creation. Caller must call DestroySharedImage to free the shared image.
   // Does not take ownership of |buffer| or retain any references to it.
-  uint32_t BindSharedBufferImage(gfx::GpuMemoryBuffer* buffer,
-                                 const gfx::Size& size,
-                                 gfx::BufferFormat format,
-                                 gfx::BufferUsage usage,
-                                 uint32_t texture_id);
+  gpu::MailboxHolder CreateSharedImage(
+      gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
+      const gfx::ColorSpace& color_space,
+      uint32_t usage);
 
-  void UnbindSharedBuffer(uint32_t image_id, uint32_t texture_id);
+  // Destroys a shared image created by CreateSharedImage. The mailbox_holder's
+  // sync_token must have been updated to a sync token after the last use of the
+  // shared image.
+  void DestroySharedImage(const gpu::MailboxHolder& mailbox_holder);
 
  private:
   void CreateContextProviderInternal();
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.cc b/chrome/browser/android/vr/web_xr_presentation_state.cc
index ddfecd7..41c64fc5 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.cc
+++ b/chrome/browser/android/vr/web_xr_presentation_state.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/android/vr/web_xr_presentation_state.h"
 
 #include "base/trace_event/trace_event.h"
-#include "gpu/command_buffer/common/mailbox_holder.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
 #include "ui/gl/gl_fence.h"
 #include "ui/gl/gl_image_egl.h"
@@ -115,6 +114,16 @@
   return can_cancel;
 }
 
+std::vector<std::unique_ptr<WebXrSharedBuffer>>
+WebXrPresentationState::TakeSharedBuffers() {
+  std::vector<std::unique_ptr<WebXrSharedBuffer>> shared_buffers;
+  for (auto& frame : frames_storage_) {
+    if (frame->shared_buffer)
+      shared_buffers.emplace_back(std::move(frame->shared_buffer));
+  }
+  return shared_buffers;
+}
+
 void WebXrPresentationState::EndPresentation() {
   TRACE_EVENT0("gpu", __FUNCTION__);
 
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.h b/chrome/browser/android/vr/web_xr_presentation_state.h
index efb8622..97bfad7 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.h
+++ b/chrome/browser/android/vr/web_xr_presentation_state.h
@@ -12,6 +12,7 @@
 #include "base/containers/queue.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/transform.h"
 
@@ -22,7 +23,6 @@
 
 namespace gpu {
 class GpuMemoryBufferImplAndroidHardwareBuffer;
-struct MailboxHolder;
 }  // namespace gpu
 
 namespace vr {
@@ -79,9 +79,7 @@
   std::unique_ptr<gpu::GpuMemoryBufferImplAndroidHardwareBuffer> gmb;
 
   // Resources in the remote GPU process command buffer context
-  std::unique_ptr<gpu::MailboxHolder> mailbox_holder;
-  uint32_t remote_texture = 0;
-  uint32_t remote_image = 0;
+  gpu::MailboxHolder mailbox_holder;
 
   // Resources in the local GL context
   uint32_t local_texture = 0;
@@ -178,6 +176,11 @@
   bool mailbox_bridge_ready() { return mailbox_bridge_ready_; }
   void NotifyMailboxBridgeReady() { mailbox_bridge_ready_ = true; }
 
+  // Extracts the shared buffers from all frames, resetting said frames to an
+  // invalid state.
+  // This is intended for resource cleanup, after EndPresentation was called.
+  std::vector<std::unique_ptr<WebXrSharedBuffer>> TakeSharedBuffers();
+
   // Used by WebVrCanAnimateFrame() to detect when ui_->CanSendWebVrVSync()
   // transitions from false to true, as part of starting the incoming frame
   // timeout.
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2ff583c6..4ca756f 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -101,6 +101,7 @@
     "//chromeos/dbus/cryptohome:attestation_proto",
     "//chromeos/dbus/cryptohome:cryptohome_proto",
     "//chromeos/dbus/cryptohome:cryptohome_signkey_proto",
+    "//chromeos/dbus/cups_proxy",
     "//chromeos/dbus/kerberos",
     "//chromeos/dbus/kerberos:kerberos_proto",
     "//chromeos/dbus/machine_learning",
@@ -1618,8 +1619,6 @@
     "policy/device_policy_decoder_chromeos.cc",
     "policy/device_policy_decoder_chromeos.h",
     "policy/device_policy_remover.h",
-    "policy/device_status_collector.cc",
-    "policy/device_status_collector.h",
     "policy/device_wallpaper_image_handler.cc",
     "policy/device_wallpaper_image_handler.h",
     "policy/device_wifi_allowed_handler.cc",
@@ -1700,6 +1699,10 @@
     "policy/server_backed_state_keys_broker.h",
     "policy/single_app_install_event_log.cc",
     "policy/single_app_install_event_log.h",
+    "policy/status_collector/device_status_collector.cc",
+    "policy/status_collector/device_status_collector.h",
+    "policy/status_collector/status_collector.cc",
+    "policy/status_collector/status_collector.h",
     "policy/status_uploader.cc",
     "policy/status_uploader.h",
     "policy/system_log_uploader.cc",
@@ -1939,6 +1942,8 @@
     "smb_client/smb_url.h",
     "smb_client/temp_file_manager.cc",
     "smb_client/temp_file_manager.h",
+    "startup_settings_cache.cc",
+    "startup_settings_cache.h",
     "system/automatic_reboot_manager.cc",
     "system/automatic_reboot_manager.h",
     "system/automatic_reboot_manager_observer.h",
@@ -2579,6 +2584,7 @@
     "smb_client/smb_task_queue_unittest.cc",
     "smb_client/smb_url_unittest.cc",
     "smb_client/temp_file_manager_unittest.cc",
+    "startup_settings_cache_unittest.cc",
     "system/automatic_reboot_manager_unittest.cc",
     "system/device_disabling_manager_unittest.cc",
     "system/procfs_util_unittest.cc",
diff --git a/chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.cc b/chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.cc
index bdf0bbf..c9b7b904 100644
--- a/chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.cc
+++ b/chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.cc
@@ -9,7 +9,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/chromeos/child_accounts/event_based_status_reporting_service_factory.h"
 #include "chrome/browser/chromeos/child_accounts/usage_time_limit_processor.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/status_uploader.h"
 #include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
 #include "chrome/browser/chromeos/policy/user_policy_manager_factory_chromeos.h"
@@ -51,6 +51,8 @@
   DCHECK(pref_service->GetInitializationStatus() !=
          PrefService::INITIALIZATION_STATUS_WAITING);
 
+  // We immediately upload a status report after Time Limits policy changes.
+  // Make sure we listen for those events.
   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
   pref_change_registrar_->Init(pref_service);
   pref_change_registrar_->Add(
@@ -83,6 +85,7 @@
 
   VLOG(1) << "Creating status uploader for consumer status reporting.";
   day_reset_time_ = new_day_reset_time;
+
   status_uploader_ = std::make_unique<policy::StatusUploader>(
       client,
       std::make_unique<policy::DeviceStatusCollector>(
@@ -102,8 +105,10 @@
 }
 
 base::TimeDelta ConsumerStatusReportingService::GetChildScreenTime() const {
-  return const_cast<policy::DeviceStatusCollector*>(
-             status_uploader_->device_status_collector())
+  // Notice that this cast works because we know that |status_uploader_| has a
+  // DeviceStatusCollector (see above).
+  return static_cast<policy::DeviceStatusCollector*>(
+             status_uploader_->status_collector())
       ->GetActiveChildScreenTime();
 }
 
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 4acfefe..8527557 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -101,6 +101,7 @@
 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/chromeos/settings/shutdown_policy_forwarder.h"
+#include "chrome/browser/chromeos/startup_settings_cache.h"
 #include "chrome/browser/chromeos/system/input_device_settings.h"
 #include "chrome/browser/chromeos/system/user_removal_manager.h"
 #include "chrome/browser/chromeos/ui/low_disk_notification.h"
@@ -152,6 +153,7 @@
 #include "components/account_id/account_id.h"
 #include "components/arc/arc_util.h"
 #include "components/device_event_log/device_event_log.h"
+#include "components/language/core/browser/pref_names.h"
 #include "components/metrics/metrics_service.h"
 #include "components/ownership/owner_key_util.h"
 #include "components/prefs/pref_service.h"
@@ -1202,6 +1204,11 @@
   g_browser_process->platform_part()->ShutdownSessionManager();
   // Ash needs to be closed before UserManager is destroyed.
   g_browser_process->platform_part()->DestroyChromeUserManager();
+
+  // See comment at startup_settings_cache::ReadAppLocale() for why we do this.
+  startup_settings_cache::WriteAppLocale(
+      g_browser_process->local_state()->GetString(
+          language::prefs::kApplicationLocale));
 }
 
 void ChromeBrowserMainPartsChromeos::PostDestroyThreads() {
diff --git a/chrome/browser/chromeos/dbus/dbus_helper.cc b/chrome/browser/chromeos/dbus/dbus_helper.cc
index c12fba2..9f6fd9b 100644
--- a/chrome/browser/chromeos/dbus/dbus_helper.cc
+++ b/chrome/browser/chromeos/dbus/dbus_helper.cc
@@ -13,6 +13,7 @@
 #include "chromeos/dbus/audio/cras_audio_client.h"
 #include "chromeos/dbus/auth_policy/auth_policy_client.h"
 #include "chromeos/dbus/biod/biod_client.h"
+#include "chromeos/dbus/cups_proxy/cups_proxy_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/kerberos/kerberos_client.h"
 #include "chromeos/dbus/machine_learning/machine_learning_client.h"
@@ -57,6 +58,7 @@
     BiodClient::Initialize(bus);  // For device::Fingerprint.
     CrasAudioClient::Initialize(bus);
     CryptohomeClient::Initialize(bus);
+    CupsProxyClient::Initialize(bus);
     KerberosClient::Initialize(bus);
     MachineLearningClient::Initialize(bus);
     MediaAnalyticsClient::Initialize(bus);
@@ -70,6 +72,7 @@
     BiodClient::InitializeFake();  // For device::Fingerprint.
     CrasAudioClient::InitializeFake();
     CryptohomeClient::InitializeFake();
+    CupsProxyClient::InitializeFake();
     KerberosClient::InitializeFake();
     MachineLearningClient::InitializeFake();
     MediaAnalyticsClient::InitializeFake();
@@ -96,6 +99,7 @@
   MediaAnalyticsClient::Shutdown();
   MachineLearningClient::Shutdown();
   KerberosClient::Shutdown();
+  CupsProxyClient::Shutdown();
   CryptohomeClient::Shutdown();
   CrasAudioClient::Shutdown();
   BiodClient::Shutdown();
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 bfef5980..1c8b56d8 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
@@ -158,7 +158,7 @@
     fake_user_manager_->CreateLocalState();
 
     // Rebuild quick unlock state.
-    quick_unlock::EnableForTesting();
+    quick_unlock::EnabledForTesting(true);
     quick_unlock::PinBackend::ResetForTesting();
 
     base::RunLoop().RunUntilIdle();
@@ -185,6 +185,7 @@
   }
 
   void TearDown() override {
+    quick_unlock::EnabledForTesting(false);
     quick_unlock::DisablePinByPolicyForTesting(false);
 
     base::RunLoop().RunUntilIdle();
diff --git a/chrome/browser/chromeos/login/active_directory_login_browsertest.cc b/chrome/browser/chromeos/login/active_directory_login_browsertest.cc
index 4024605..867628f 100644
--- a/chrome/browser/chromeos/login/active_directory_login_browsertest.cc
+++ b/chrome/browser/chromeos/login/active_directory_login_browsertest.cc
@@ -5,318 +5,75 @@
 #include <string>
 
 #include "base/base_paths.h"
-#include "base/command_line.h"
 #include "base/environment.h"
-#include "base/location.h"
 #include "base/path_service.h"
-#include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/authpolicy/kerberos_files_handler.h"
-#include "chrome/browser/chromeos/login/active_directory_test_helper.h"
-#include "chrome/browser/chromeos/login/login_manager_test.h"
-#include "chrome/browser/chromeos/login/login_shelf_test_helper.h"
-#include "chrome/browser/chromeos/login/startup_utils.h"
-#include "chrome/browser/chromeos/login/test/js_checker.h"
-#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
-#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/chromeos/login/test/active_directory_login_mixin.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
-#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
-#include "chrome/grit/generated_resources.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/interactive_test_utils.h"
-#include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/dbus/auth_policy/fake_auth_policy_client.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/tpm/stub_install_attributes.h"
-#include "components/account_id/account_id.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/common/network_service_util.h"
 #include "content/public/common/service_manager_connection.h"
 #include "content/public/common/service_names.mojom.h"
-#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
 #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
 #include "services/network/public/mojom/network_service_test.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/geometry/test/rect_test_util.h"
-
-using ::gfx::test::RectContains;
 
 namespace chromeos {
+
 namespace {
 
-const char kPassword[] = "password";
-
-constexpr char kGaiaSigninId[] = "signin-frame-dialog";
-constexpr char kAdOfflineAuthId[] = "offline-ad-auth";
-
 constexpr char kTestActiveDirectoryUser[] = "test-user";
 constexpr char kTestUserRealm[] = "user.realm";
-constexpr char kAdMachineInput[] = "machineNameInput";
-constexpr char kAdMoreOptionsButton[] = "moreOptionsBtn";
-constexpr char kAdUserInput[] = "userInput";
-constexpr char kAdPasswordInput[] = "passwordInput";
-constexpr char kAdCredsButton[] = "nextButton";
-constexpr char kAdAutocompleteRealm[] = "$.userInput.querySelector('span')";
-
-constexpr char kAdPasswordChangeId[] = "active-directory-password-change";
-constexpr char kAdAnimatedPages[] = "animatedPages";
-constexpr char kAdOldPasswordInput[] = "oldPassword";
-constexpr char kAdNewPassword1Input[] = "newPassword1";
-constexpr char kAdNewPassword2Input[] = "newPassword2";
-constexpr char kAdPasswordChangeFormId[] = "inputForm";
-constexpr char kFormButtonId[] = "button";
+constexpr char kPassword[] = "password";
 constexpr char kNewPassword[] = "new_password";
 constexpr char kDifferentNewPassword[] = "different_new_password";
 
-constexpr char kNavigationId[] = "navigation";
-constexpr char kCloseButtonId[] = "closeButton";
+void AssertNetworkServiceEnvEquals(const std::string& name,
+                                   const std::string& expected_value) {
+  std::string value;
+  if (content::IsOutOfProcessNetworkService()) {
+    network::mojom::NetworkServiceTestPtr network_service_test;
+    content::ServiceManagerConnection::GetForProcess()
+        ->GetConnector()
+        ->BindInterface(content::mojom::kNetworkServiceName,
+                        &network_service_test);
+    mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+    network_service_test->GetEnvironmentVariableValue(name, &value);
+  } else {
+    // If the network service is running in-process, we can read the
+    // environment variable directly.
+    base::Environment::Create()->GetVar(name, &value);
+  }
+  EXPECT_EQ(value, expected_value);
+}
 
-class ActiveDirectoryLoginTest : public LoginManagerTest {
+class ActiveDirectoryLoginTest : public OobeBaseTest {
  public:
   ActiveDirectoryLoginTest()
-      : LoginManagerTest(true, true),
-        // Using the same realm as supervised user domain. Should be treated as
-        // normal realm.
+      : OobeBaseTest(),
+        // Using the same realm as supervised user domain. Should be treated
+        // as normal realm.
         test_realm_(user_manager::kSupervisedUserDomain),
-        test_user_(kTestActiveDirectoryUser + ("@" + test_realm_)),
-        install_attributes_(
-            chromeos::StubInstallAttributes::CreateActiveDirectoryManaged(
-                test_realm_,
-                "device_id")) {}
+        test_user_(kTestActiveDirectoryUser + ("@" + test_realm_)) {}
 
   ~ActiveDirectoryLoginTest() override = default;
 
-  void SetUpInProcessBrowserTestFixture() override {
-    LoginManagerTest::SetUpInProcessBrowserTestFixture();
-
-    // This is called before ChromeBrowserMain initializes the fake dbus
-    // clients, and DisableOperationDelayForTesting() needs to be called before
-    // other ChromeBrowserMain initialization occurs.
-    AuthPolicyClient::InitializeFake();
-    FakeAuthPolicyClient::Get()->DisableOperationDelayForTesting();
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    LoginManagerTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitch(switches::kOobeSkipPostLogin);
-  }
-
-  void SetUpOnMainThread() override {
-    // Set the threshold to a max value to disable the offline message screen
-    // on slow configurations like MSAN, where it otherwise triggers on every
-    // run.
-    LoginDisplayHost::default_host()
-        ->GetOobeUI()
-        ->signin_screen_handler()
-        ->SetOfflineTimeoutForTesting(base::TimeDelta::Max());
-    LoginManagerTest::SetUpOnMainThread();
-  }
-
-  void TriggerPasswordChangeScreen() {
-    OobeScreenWaiter screen_waiter(
-        OobeScreen::SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE);
-
-    fake_auth_policy_client()->set_auth_error(
-        authpolicy::ERROR_PASSWORD_EXPIRED);
-    SubmitActiveDirectoryCredentials(test_user_, kPassword);
-    screen_waiter.Wait();
-    TestAdPasswordChangeError(std::string());
-  }
-
-  void ClosePasswordChangeScreen() {
-    test::OobeJS().TapOnPath(
-        {kAdPasswordChangeId, kNavigationId, kCloseButtonId});
-  }
-
-  void ExpectValid(const std::string& parent_id,
-                   const std::string& child_id,
-                   bool valid) {
-    std::string js =
-        test::GetOobeElementPath({parent_id, child_id}) + ".invalid";
-    if (valid)
-      test::OobeJS().ExpectFalse(js);
-    else
-      test::OobeJS().ExpectTrue(js);
-  }
-
-  // Checks if Active Directory login is visible.
-  void TestLoginVisible() {
-    OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_GAIA_SIGNIN);
-    screen_waiter.Wait();
-    // Checks if Gaia signin is hidden.
-    test::OobeJS().ExpectHidden(kGaiaSigninId);
-
-    // Checks if Active Directory signin is visible.
-    test::OobeJS().ExpectVisible(kAdOfflineAuthId);
-    test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMachineInput});
-    test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMoreOptionsButton});
-    test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdUserInput});
-    test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdPasswordInput});
-
-    std::string autocomplete_domain_ui;
-    base::TrimString(
-        test::OobeJS().GetString(
-            JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".innerText"),
-        base::kWhitespaceASCII, &autocomplete_domain_ui);
-    // Checks if realm is set to autocomplete username.
-    EXPECT_EQ(autocomplete_realm_, autocomplete_domain_ui);
-
-    EXPECT_TRUE(LoginShelfTestHelper().IsLoginShelfShown());
-  }
-
-  // Checks if Active Directory password change screen is shown.
-  void TestPasswordChangeVisible() {
-    // Checks if Gaia signin is hidden.
-    test::OobeJS().ExpectHidden(kGaiaSigninId);
-    // Checks if Active Directory signin is visible.
-    test::OobeJS().ExpectVisible(kAdPasswordChangeId);
-    test::OobeJS().ExpectTrue(
-        test::GetOobeElementPath({kAdPasswordChangeId, kAdAnimatedPages}) +
-        ".selected == 0");
-    test::OobeJS().ExpectVisiblePath(
-        {kAdPasswordChangeId, kNavigationId, kCloseButtonId});
-  }
-
-  // Checks if user input is marked as invalid.
-  void TestUserError() {
-    TestLoginVisible();
-    ExpectValid(kAdOfflineAuthId, kAdUserInput, false);
-  }
-
-  void SetUserInput(const std::string& value) {
-    test::OobeJS().TypeIntoPath(value, {kAdOfflineAuthId, kAdUserInput});
-  }
-
-  void TestUserInput(const std::string& value) {
-    test::OobeJS().ExpectEQ(
-        test::GetOobeElementPath({kAdOfflineAuthId, kAdUserInput}) + ".value",
-        value);
-  }
-
-  // Checks if password input is marked as invalid.
-  void TestPasswordError() {
-    TestLoginVisible();
-    ExpectValid(kAdOfflineAuthId, kAdPasswordInput, false);
-  }
-
-  // Checks that machine, password and user inputs are valid.
-  void TestNoError() {
-    TestLoginVisible();
-    ExpectValid(kAdOfflineAuthId, kAdMachineInput, true);
-    ExpectValid(kAdOfflineAuthId, kAdUserInput, true);
-    ExpectValid(kAdOfflineAuthId, kAdPasswordInput, true);
-  }
-
-  // Checks if autocomplete domain is visible for the user input.
-  void TestDomainVisible() {
-    test::OobeJS().ExpectTrue(
-        "!" + JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".hidden");
-  }
-
-  // Checks if autocomplete domain is hidden for the user input.
-  void TestDomainHidden() {
-    test::OobeJS().ExpectTrue(
-        JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".hidden");
-  }
-
-  // Checks if Active Directory password change screen is shown. Also checks if
-  // |invalid_element| is invalidated and all the other elements are valid.
-  void TestAdPasswordChangeError(const std::string& invalid_element) {
-    TestPasswordChangeVisible();
-    for (const char* element :
-         {kAdOldPasswordInput, kAdNewPassword1Input, kAdNewPassword2Input}) {
-      std::string js_assertion =
-          test::GetOobeElementPath({kAdPasswordChangeId, element}) +
-          ".isInvalid";
-      if (element != invalid_element)
-        js_assertion = "!" + js_assertion;
-      test::OobeJS().ExpectTrue(js_assertion);
-    }
-  }
-
-  // Sets username and password for the Active Directory login and submits it.
-  void SubmitActiveDirectoryCredentials(const std::string& username,
-                                        const std::string& password) {
-    test::OobeJS().TypeIntoPath(username, {kAdOfflineAuthId, kAdUserInput});
-    test::OobeJS().TypeIntoPath(password, {kAdOfflineAuthId, kAdPasswordInput});
-    test::OobeJS().TapOnPath({kAdOfflineAuthId, kAdCredsButton});
-  }
-
-  // Sets username and password for the Active Directory login and submits it.
-  void SubmitActiveDirectoryPasswordChangeCredentials(
-      const std::string& old_password,
-      const std::string& new_password1,
-      const std::string& new_password2) {
-    test::OobeJS().TypeIntoPath(old_password,
-                                {kAdPasswordChangeId, kAdOldPasswordInput});
-    test::OobeJS().TypeIntoPath(new_password1,
-                                {kAdPasswordChangeId, kAdNewPassword1Input});
-    test::OobeJS().TypeIntoPath(new_password2,
-                                {kAdPasswordChangeId, kAdNewPassword2Input});
-    test::OobeJS().TapOnPath(
-        {kAdPasswordChangeId, kAdPasswordChangeFormId, kFormButtonId});
-  }
-
-  void SetupActiveDirectoryJSNotifications() {
-    test::OobeJS().Evaluate(
-        "var testInvalidateAd = login.GaiaSigninScreen.invalidateAd;"
-        "login.GaiaSigninScreen.invalidateAd = function(user, errorState) {"
-        "  testInvalidateAd(user, errorState);"
-        "  window.domAutomationController.send('ShowAuthError');"
-        "}");
-  }
-
-  void WaitForMessage(content::DOMMessageQueue* message_queue,
-                      const std::string& expected_message) {
-    std::string message;
-    do {
-      ASSERT_TRUE(message_queue->WaitForMessage(&message));
-    } while (message != expected_message);
-  }
-
-  void AssertNetworkServiceEnvEquals(const std::string& name,
-                                     const std::string& expected_value) {
-    std::string value;
-    if (content::IsOutOfProcessNetworkService()) {
-      network::mojom::NetworkServiceTestPtr network_service_test;
-      content::ServiceManagerConnection::GetForProcess()
-          ->GetConnector()
-          ->BindInterface(content::mojom::kNetworkServiceName,
-                          &network_service_test);
-      mojo::ScopedAllowSyncCallForTesting allow_sync_call;
-      network_service_test->GetEnvironmentVariableValue(name, &value);
-    } else {
-      // If the network service is running in-process, we can read the
-      // environment variable directly.
-      base::Environment::Create()->GetVar(name, &value);
-    }
-    EXPECT_EQ(value, expected_value);
-  }
-
  protected:
-  // Returns string representing element with id=|element_id| inside Active
-  // Directory login element.
-  std::string JSElement(const std::string& parent_id,
-                        const std::string& selector) {
-    return "document.querySelector('#" + parent_id + "')." + selector;
-  }
   FakeAuthPolicyClient* fake_auth_policy_client() {
     return FakeAuthPolicyClient::Get();
   }
 
   const std::string test_realm_;
   const std::string test_user_;
-  std::string autocomplete_realm_;
+  ActiveDirectoryLoginMixin ad_login_{&mixin_host_, test_realm_};
 
  private:
-  chromeos::ScopedStubInstallAttributes install_attributes_;
-
   DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryLoginTest);
 };
 
@@ -331,42 +88,40 @@
         ->set_login_screen_domain_auto_complete(kTestUserRealm);
     fake_auth_policy_client()->set_device_policy(device_settings);
     autocomplete_realm_ = "@" + std::string(kTestUserRealm);
+    ad_login_.set_autocomplete_realm(autocomplete_realm_);
   }
 
+  std::string autocomplete_realm_;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryLoginAutocompleteTest);
 };
+
 }  // namespace
 
-// Declares a PRE_ test that calls StartupUtils::MarkOobeCompleted() and the
-// test itself.
-#define IN_PROC_BROWSER_TEST_F_WITH_PRE(class_name, test_name) \
-  IN_PROC_BROWSER_TEST_F(class_name, PRE_##test_name) {        \
-    StartupUtils::MarkOobeCompleted();                         \
-  }                                                            \
-  IN_PROC_BROWSER_TEST_F(class_name, test_name)
-
 // Test successful Active Directory login.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest, LoginSuccess) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest, LoginSuccess) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  TestNoError();
-  TestDomainHidden();
+  ad_login_.TestNoError();
+  ad_login_.TestDomainHidden();
   content::WindowedNotificationObserver session_start_waiter(
       chrome::NOTIFICATION_SESSION_STARTED,
       content::NotificationService::AllSources());
-  SubmitActiveDirectoryCredentials(test_user_, kPassword);
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, kPassword);
   session_start_waiter.Wait();
 }
 
 // Tests that the Kerberos SSO environment variables are set correctly after
 // an Active Directory log in.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest, KerberosVarsCopied) {
-  TestNoError();
-  TestDomainHidden();
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest, KerberosVarsCopied) {
+  OobeBaseTest::WaitForSigninScreen();
+  ad_login_.TestNoError();
+  ad_login_.TestDomainHidden();
   content::WindowedNotificationObserver session_start_waiter(
       chrome::NOTIFICATION_SESSION_STARTED,
       content::NotificationService::AllSources());
-  SubmitActiveDirectoryCredentials(test_user_, kPassword);
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, kPassword);
   session_start_waiter.Wait();
 
   base::FilePath dir;
@@ -380,166 +135,164 @@
 }
 
 // Test different UI errors for Active Directory login.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest, LoginErrors) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest, LoginErrors) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  SetupActiveDirectoryJSNotifications();
-  TestNoError();
-  TestDomainHidden();
+  ad_login_.TestNoError();
+  ad_login_.TestDomainHidden();
 
-  content::DOMMessageQueue message_queue;
+  ad_login_.SubmitActiveDirectoryCredentials("", "");
+  ad_login_.TestUserError();
+  ad_login_.TestDomainHidden();
 
-  SubmitActiveDirectoryCredentials("", "");
-  TestUserError();
-  TestDomainHidden();
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, "");
+  ad_login_.TestPasswordError();
+  ad_login_.TestDomainHidden();
 
-  SubmitActiveDirectoryCredentials(test_user_, "");
-  TestPasswordError();
-  TestDomainHidden();
-
-  SubmitActiveDirectoryCredentials(std::string(kTestActiveDirectoryUser) + "@",
-                                   kPassword);
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
-  TestUserError();
-  TestDomainHidden();
+  ad_login_.SubmitActiveDirectoryCredentials(
+      std::string(kTestActiveDirectoryUser) + "@", kPassword);
+  ad_login_.WaitForAuthError();
+  ad_login_.TestUserError();
+  ad_login_.TestDomainHidden();
 
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_USER_NAME);
-  SubmitActiveDirectoryCredentials(test_user_, kPassword);
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
-  TestUserError();
-  TestDomainHidden();
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, kPassword);
+  ad_login_.WaitForAuthError();
+  ad_login_.TestUserError();
+  ad_login_.TestDomainHidden();
 
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
-  SubmitActiveDirectoryCredentials(test_user_, kPassword);
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
-  TestPasswordError();
-  TestDomainHidden();
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, kPassword);
+  ad_login_.WaitForAuthError();
+  ad_login_.TestPasswordError();
+  ad_login_.TestDomainHidden();
 
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_UNKNOWN);
-  SubmitActiveDirectoryCredentials(test_user_, kPassword);
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, kPassword);
+  ad_login_.WaitForAuthError();
   // Inputs are not invalidated for the unknown error.
-  TestNoError();
-  TestDomainHidden();
+  ad_login_.TestNoError();
+  ad_login_.TestDomainHidden();
 }
 
 // Test successful Active Directory login from the password change screen.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
-                                PasswordChange_LoginSuccess) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest, PasswordChange_LoginSuccess) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  TestLoginVisible();
-  TestDomainHidden();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainHidden();
 
-  TriggerPasswordChangeScreen();
+  ad_login_.TriggerPasswordChangeScreen();
 
   // Password accepted by AuthPolicyClient.
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_NONE);
   content::WindowedNotificationObserver session_start_waiter(
       chrome::NOTIFICATION_SESSION_STARTED,
       content::NotificationService::AllSources());
-  SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
-                                                 kNewPassword);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials(
+      kPassword, kNewPassword, kNewPassword);
   session_start_waiter.Wait();
 }
 
 // Test different UI errors for Active Directory password change screen.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
-                                PasswordChange_UIErrors) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest, PasswordChange_UIErrors) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  TestLoginVisible();
-  TestDomainHidden();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainHidden();
 
-  TriggerPasswordChangeScreen();
+  ad_login_.TriggerPasswordChangeScreen();
   // Password rejected by UX.
   // Empty passwords.
-  SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
-  TestAdPasswordChangeError(kAdOldPasswordInput);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
+  ad_login_.TestPasswordChangeOldPasswordError();
 
   // Empty new password.
-  SubmitActiveDirectoryPasswordChangeCredentials(kPassword, "", "");
-  TestAdPasswordChangeError(kAdNewPassword1Input);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials(kPassword, "", "");
+  ad_login_.TestPasswordChangeNewPasswordError();
 
   // Empty confirmation of the new password.
-  SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword, "");
-  TestAdPasswordChangeError(kAdNewPassword2Input);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials(kPassword,
+                                                           kNewPassword, "");
+  ad_login_.TestPasswordChangeConfirmNewPasswordError();
 
   // Confirmation of password is different from new password.
-  SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
-                                                 kDifferentNewPassword);
-  TestAdPasswordChangeError(kAdNewPassword2Input);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials(
+      kPassword, kNewPassword, kDifferentNewPassword);
+  ad_login_.TestPasswordChangeConfirmNewPasswordError();
 
   // Password rejected by AuthPolicyClient.
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
-  SubmitActiveDirectoryPasswordChangeCredentials(kPassword, kNewPassword,
-                                                 kNewPassword);
-  TestAdPasswordChangeError(kAdOldPasswordInput);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials(
+      kPassword, kNewPassword, kNewPassword);
+  ad_login_.TestPasswordChangeOldPasswordError();
 }
 
 // Test reopening Active Directory password change screen clears errors.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginTest,
-                                PasswordChange_ReopenClearErrors) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginTest,
+                       PasswordChange_ReopenClearErrors) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  TestLoginVisible();
-  TestDomainHidden();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainHidden();
 
-  TriggerPasswordChangeScreen();
+  ad_login_.TriggerPasswordChangeScreen();
 
   // Empty new password.
-  SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
-  TestAdPasswordChangeError(kAdOldPasswordInput);
+  ad_login_.SubmitActiveDirectoryPasswordChangeCredentials("", "", "");
+  ad_login_.TestPasswordChangeOldPasswordError();
 
-  ClosePasswordChangeScreen();
-  TestLoginVisible();
-  TriggerPasswordChangeScreen();
+  ad_login_.ClosePasswordChangeScreen();
+  ad_login_.TestLoginVisible();
+  ad_login_.TriggerPasswordChangeScreen();
+  ad_login_.TestPasswordChangeNoErrors();
 }
 
 // Tests that autocomplete works. Submits username without domain.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginAutocompleteTest,
-                                LoginSuccess) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginAutocompleteTest, LoginSuccess) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  TestNoError();
-  TestDomainVisible();
+  ad_login_.TestNoError();
+  ad_login_.TestDomainVisible();
 
   content::WindowedNotificationObserver session_start_waiter(
       chrome::NOTIFICATION_SESSION_STARTED,
       content::NotificationService::AllSources());
-  SubmitActiveDirectoryCredentials(kTestActiveDirectoryUser, kPassword);
+  ad_login_.SubmitActiveDirectoryCredentials(kTestActiveDirectoryUser,
+                                             kPassword);
   session_start_waiter.Wait();
 }
 
 // Tests that user could override autocomplete domain.
-IN_PROC_BROWSER_TEST_F_WITH_PRE(ActiveDirectoryLoginAutocompleteTest,
-                                TestAutocomplete) {
+IN_PROC_BROWSER_TEST_F(ActiveDirectoryLoginAutocompleteTest, TestAutocomplete) {
+  OobeBaseTest::WaitForSigninScreen();
   ASSERT_TRUE(InstallAttributes::Get()->IsActiveDirectoryManaged());
-  SetupActiveDirectoryJSNotifications();
 
-  TestLoginVisible();
-  TestDomainVisible();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainVisible();
   fake_auth_policy_client()->set_auth_error(authpolicy::ERROR_BAD_PASSWORD);
-  content::DOMMessageQueue message_queue;
 
   // Submit with a different domain.
-  SetUserInput(test_user_);
-  TestDomainHidden();
-  TestUserInput(test_user_);
-  SubmitActiveDirectoryCredentials(test_user_, "password");
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
-  TestLoginVisible();
-  TestDomainHidden();
-  TestUserInput(test_user_);
+  ad_login_.SetUserInput(test_user_);
+  ad_login_.TestDomainHidden();
+  ad_login_.TestUserInput(test_user_);
+  ad_login_.SubmitActiveDirectoryCredentials(test_user_, "password");
+  ad_login_.WaitForAuthError();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainHidden();
+  ad_login_.TestUserInput(test_user_);
 
   // Set userinput with the autocomplete domain. JS will remove the autocomplete
   // domain.
-  SetUserInput(kTestActiveDirectoryUser + autocomplete_realm_);
-  TestDomainVisible();
-  TestUserInput(kTestActiveDirectoryUser);
-  SubmitActiveDirectoryCredentials(
+  ad_login_.SetUserInput(kTestActiveDirectoryUser + autocomplete_realm_);
+  ad_login_.TestDomainVisible();
+  ad_login_.TestUserInput(kTestActiveDirectoryUser);
+  ad_login_.SubmitActiveDirectoryCredentials(
       kTestActiveDirectoryUser + autocomplete_realm_, "password");
-  WaitForMessage(&message_queue, "\"ShowAuthError\"");
-  TestLoginVisible();
-  TestDomainVisible();
-  TestUserInput(kTestActiveDirectoryUser);
+  ad_login_.WaitForAuthError();
+  ad_login_.TestLoginVisible();
+  ad_login_.TestDomainVisible();
+  ad_login_.TestUserInput(kTestActiveDirectoryUser);
 }
 
-#undef IN_PROC_BROWSER_TEST_F_WITH_PRE
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc
index 2b01df5c..1817e8c 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -1068,21 +1068,6 @@
   if (auth_status_consumer_)
     auth_status_consumer_->OnPasswordChangeDetected();
 
-  // If the password change happens after an online auth, do a TokenHandle check
-  // to find out whether the user password is really changed or not.
-  // TODO(xiyuan): Remove channel restriction. See http://crbug.com/585530
-  if (chrome::GetChannel() <= version_info::Channel::DEV &&
-      auth_mode() == LoginPerformer::AUTH_MODE_EXTENSION) {
-    token_handle_util_.reset(new TokenHandleUtil);
-    if (token_handle_util_->HasToken(last_login_attempt_account_id_)) {
-      token_handle_util_->CheckToken(
-          last_login_attempt_account_id_,
-          base::Bind(&ExistingUserController::OnTokenHandleChecked,
-                     weak_factory_.GetWeakPtr()));
-      return;
-    }
-  }
-
   ShowPasswordChangedDialog();
 }
 
@@ -1820,24 +1805,6 @@
   PerformLogin(user_context, LoginPerformer::AUTH_MODE_EXTENSION);
 }
 
-void ExistingUserController::OnTokenHandleChecked(
-    const AccountId&,
-    TokenHandleUtil::TokenHandleStatus token_handle_status) {
-  // If TokenHandle is invalid or unknown, continue with regular password
-  // changed flow.
-  if (token_handle_status != TokenHandleUtil::VALID) {
-    VLOG(1) << "Checked TokenHandle status=" << token_handle_status;
-    ShowPasswordChangedDialog();
-    return;
-  }
-
-  // Otherwise, show the unrecoverable cryptohome error UI and ask user's
-  // permission to collect a feedback.
-  RecordPasswordChangeFlow(LOGIN_PASSWORD_CHANGE_FLOW_CRYPTOHOME_FAILURE);
-  VLOG(1) << "Show unrecoverable cryptohome error dialog.";
-  GetLoginDisplay()->ShowUnrecoverableCrypthomeErrorDialog();
-}
-
 void ExistingUserController::ClearRecordedNames() {
   display_email_.clear();
   display_name_.clear();
diff --git a/chrome/browser/chromeos/login/existing_user_controller.h b/chrome/browser/chromeos/login/existing_user_controller.h
index 8f84c10..499ffa08 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.h
+++ b/chrome/browser/chromeos/login/existing_user_controller.h
@@ -22,7 +22,6 @@
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/login/screens/encryption_migration_mode.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
-#include "chrome/browser/chromeos/login/signin/token_handle_util.h"
 #include "chrome/browser/chromeos/login/ui/login_display.h"
 #include "chrome/browser/chromeos/policy/minimum_version_policy_handler.h"
 #include "chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h"
@@ -295,11 +294,6 @@
   // Callback invoked when |oauth2_token_initializer_| has finished.
   void OnOAuth2TokensFetched(bool success, const UserContext& user_context);
 
-  // Callback invoked when |token_handle_util_| finishes token check.
-  void OnTokenHandleChecked(
-      const AccountId&,
-      TokenHandleUtil::TokenHandleStatus token_handle_status);
-
   // Called on completition of a pre-signin policy fetch, which is performed to
   // check if there is a user policy governing migration action.
   void OnPolicyFetchResult(
@@ -416,8 +410,6 @@
 
   std::unique_ptr<OAuth2TokenInitializer> oauth2_token_initializer_;
 
-  std::unique_ptr<TokenHandleUtil> token_handle_util_;
-
   std::unique_ptr<policy::PreSigninPolicyFetcher> pre_signin_policy_fetcher_;
 
   // Used to wait for cloud policy store load during public session login, if
diff --git a/chrome/browser/chromeos/login/lock/screen_locker_browsertest.cc b/chrome/browser/chromeos/login/lock/screen_locker_browsertest.cc
index 2369a05..a080c88 100644
--- a/chrome/browser/chromeos/login/lock/screen_locker_browsertest.cc
+++ b/chrome/browser/chromeos/login/lock/screen_locker_browsertest.cc
@@ -70,8 +70,10 @@
             ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   }
 
+  void TearDown() override { quick_unlock::EnabledForTesting(false); }
+
   void EnrollFingerprint() {
-    quick_unlock::EnableForTesting();
+    quick_unlock::EnabledForTesting(true);
 
     FakeBiodClient::Get()->StartEnrollSession(
         "test-user", std::string(),
diff --git a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc
index e181424..e3b35ecb 100644
--- a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc
+++ b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc
@@ -27,6 +27,9 @@
 
 void InProcessBrowserTestMixin::SetUpInProcessBrowserTestFixture() {}
 
+void InProcessBrowserTestMixin::CreatedBrowserMainParts(
+    content::BrowserMainParts* browser_main_parts) {}
+
 void InProcessBrowserTestMixin::SetUpOnMainThread() {}
 
 void InProcessBrowserTestMixin::TearDownOnMainThread() {}
@@ -61,6 +64,12 @@
     mixin->SetUpInProcessBrowserTestFixture();
 }
 
+void InProcessBrowserTestMixinHost::CreatedBrowserMainParts(
+    content::BrowserMainParts* browser_main_parts) {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->CreatedBrowserMainParts(browser_main_parts);
+}
+
 void InProcessBrowserTestMixinHost::SetUpOnMainThread() {
   for (InProcessBrowserTestMixin* mixin : mixins_)
     mixin->SetUpOnMainThread();
@@ -107,6 +116,12 @@
   InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
 }
 
+void MixinBasedInProcessBrowserTest::CreatedBrowserMainParts(
+    content::BrowserMainParts* browser_main_parts) {
+  mixin_host_.CreatedBrowserMainParts(browser_main_parts);
+  InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
+}
+
 void MixinBasedInProcessBrowserTest::SetUpOnMainThread() {
   mixin_host_.SetUpOnMainThread();
   InProcessBrowserTest::SetUpOnMainThread();
diff --git a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h
index f3089439..0ed02b9 100644
--- a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h
+++ b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h
@@ -72,6 +72,7 @@
   //   SetUpCommandLine
   //   SetUpDefaultCommandLine
   //   SetUpInProcessBrowserTestFixture
+  //   CreatedBrowserMainParts
   //   SetUpOnMainThread
   //   TearDownOnMainThread
   //   TearDownInProcessBrowserTestFixture
@@ -83,6 +84,8 @@
   virtual void SetUpCommandLine(base::CommandLine* command_line);
   virtual void SetUpDefaultCommandLine(base::CommandLine* command_line);
   virtual void SetUpInProcessBrowserTestFixture();
+  virtual void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts);
   virtual void SetUpOnMainThread();
   virtual void TearDownOnMainThread();
   virtual void TearDownInProcessBrowserTestFixture();
@@ -102,6 +105,7 @@
   void SetUpCommandLine(base::CommandLine* command_line);
   void SetUpDefaultCommandLine(base::CommandLine* command_line);
   void SetUpInProcessBrowserTestFixture();
+  void CreatedBrowserMainParts(content::BrowserMainParts* browser_main_parts);
   void SetUpOnMainThread();
   void TearDownOnMainThread();
   void TearDownInProcessBrowserTestFixture();
@@ -129,6 +133,8 @@
   void SetUpCommandLine(base::CommandLine* command_line) override;
   void SetUpDefaultCommandLine(base::CommandLine* command_line) override;
   void SetUpInProcessBrowserTestFixture() override;
+  void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts) override;
   void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
   void TearDownInProcessBrowserTestFixture() override;
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index c54dc78..84eec27 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -80,6 +80,7 @@
   }
 
   void TearDown() override {
+    quick_unlock::EnabledForTesting(false);
     OobeBaseTest::TearDown();
     params_.reset();
   }
@@ -95,7 +96,7 @@
     OobeBaseTest::SetUpInProcessBrowserTestFixture();
 
     if (params_->is_quick_unlock_enabled)
-      quick_unlock::EnableForTesting();
+      quick_unlock::EnabledForTesting(true);
   }
 
   void TearDownOnMainThread() override {
diff --git a/chrome/browser/chromeos/login/oobe_screen.cc b/chrome/browser/chromeos/login/oobe_screen.cc
index 7d03f63..60e0753 100644
--- a/chrome/browser/chromeos/login/oobe_screen.cc
+++ b/chrome/browser/chromeos/login/oobe_screen.cc
@@ -43,7 +43,6 @@
     "confirm-password",                // SCREEN_CONFIRM_PASSWORD
     "fatal-error",                     // SCREEN_FATAL_ERROR
     "device-disabled",                 // SCREEN_DEVICE_DISABLED
-    "unrecoverable-cryptohome-error",  // SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR
     "userBoard",                       // SCREEN_USER_SELECTION
     "ad-password-change",            // SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE
     "encryption-migration",          // SCREEN_ENCRYPTION_MIGRATION
diff --git a/chrome/browser/chromeos/login/oobe_screen.h b/chrome/browser/chromeos/login/oobe_screen.h
index 3beb845..c2e626f 100644
--- a/chrome/browser/chromeos/login/oobe_screen.h
+++ b/chrome/browser/chromeos/login/oobe_screen.h
@@ -40,7 +40,6 @@
   SCREEN_CONFIRM_PASSWORD,
   SCREEN_FATAL_ERROR,
   SCREEN_DEVICE_DISABLED,
-  SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR,
   SCREEN_USER_SELECTION,
   SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE,
   SCREEN_ENCRYPTION_MIGRATION,
diff --git a/chrome/browser/chromeos/login/password_change_browsertest.cc b/chrome/browser/chromeos/login/password_change_browsertest.cc
new file mode 100644
index 0000000..070d500
--- /dev/null
+++ b/chrome/browser/chromeos/login/password_change_browsertest.cc
@@ -0,0 +1,360 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/chromeos/login/existing_user_controller.h"
+#include "chrome/browser/chromeos/login/login_manager_test.h"
+#include "chrome/browser/chromeos/login/session/user_session_manager.h"
+#include "chrome/browser/chromeos/login/session/user_session_manager_test_api.h"
+#include "chrome/browser/chromeos/login/signin_specifics.h"
+#include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chromeos/login/auth/stub_authenticator.h"
+#include "chromeos/login/auth/stub_authenticator_builder.h"
+#include "chromeos/login/auth/user_context.h"
+#include "components/account_id/account_id.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/session_manager/core/session_manager_observer.h"
+#include "components/user_manager/user_manager.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr char kTestUser[] = "test-user1@gmail.com";
+constexpr char kTestUserGaiaId[] = "test-user1@gmail.com";
+constexpr char kPassword[] = "test user password";
+
+// Waits for the window that hosts OOBE UI changes visibility to target value.
+// When waiting for the OOBE UI window to be hidden, it handles the window
+// getting destroyed. Window getting destroyed while waiting for the window
+// to become visible will stop the waiter, but will cause a test failure.
+class OobeUiWindowVisibilityWaiter : public aura::WindowObserver {
+ public:
+  explicit OobeUiWindowVisibilityWaiter(bool target_visibilty)
+      : target_visibility_(target_visibilty) {}
+  ~OobeUiWindowVisibilityWaiter() override = default;
+
+  void Wait() {
+    aura::Window* window = GetWindow();
+    if (!window && !target_visibility_)
+      return;
+
+    DCHECK(window);
+    if (target_visibility_ == window->IsVisible())
+      return;
+
+    base::RunLoop run_loop;
+    wait_stop_closure_ = run_loop.QuitClosure();
+    window_observer_.Add(window);
+    run_loop.Run();
+  }
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
+    if (visible != target_visibility_)
+      return;
+    window_observer_.RemoveAll();
+    std::move(wait_stop_closure_).Run();
+  }
+
+  void OnWindowDestroyed(aura::Window* window) override {
+    EXPECT_FALSE(target_visibility_);
+    window_observer_.RemoveAll();
+    std::move(wait_stop_closure_).Run();
+  }
+
+ private:
+  aura::Window* GetWindow() {
+    LoginDisplayHost* host = LoginDisplayHost::default_host();
+    if (!host || !host->GetOobeWebContents())
+      return nullptr;
+    return host->GetOobeWebContents()->GetTopLevelNativeWindow();
+  }
+
+  const bool target_visibility_;
+  base::OnceClosure wait_stop_closure_;
+  ScopedObserver<aura::Window, OobeUiWindowVisibilityWaiter> window_observer_{
+      this};
+};
+
+// Used to wait for session manager to become active.
+class SessionStartWaiter : public session_manager::SessionManagerObserver {
+ public:
+  SessionStartWaiter() = default;
+  ~SessionStartWaiter() override = default;
+
+  void Wait() {
+    if (!session_manager::SessionManager::Get()->IsUserSessionBlocked())
+      return;
+    session_observer_.Add(session_manager::SessionManager::Get());
+
+    base::RunLoop run_loop;
+    session_active_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+
+    session_observer_.RemoveAll();
+  }
+
+  // session_manager::SessionManagerObserver:
+  void OnSessionStateChanged() override {
+    if (!session_manager::SessionManager::Get()->IsUserSessionBlocked() &&
+        session_active_callback_) {
+      std::move(session_active_callback_).Run();
+    }
+  }
+
+ private:
+  base::OnceClosure session_active_callback_;
+  ScopedObserver<session_manager::SessionManager, SessionStartWaiter>
+      session_observer_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(SessionStartWaiter);
+};
+
+}  // namespace
+
+class PasswordChangeTest : public LoginManagerTest {
+ public:
+  PasswordChangeTest()
+      : LoginManagerTest(false, false),
+        test_account_id_(
+            AccountId::FromUserEmailGaiaId(kTestUser, kTestUserGaiaId)) {
+    set_force_webui_login(false);
+  }
+  ~PasswordChangeTest() override = default;
+
+  UserContext GetTestUserContext() {
+    const user_manager::User* user =
+        user_manager::UserManager::Get()->FindUser(test_account_id_);
+    UserContext user_context(*user);
+    user_context.SetKey(Key(kPassword));
+    user_context.SetUserIDHash(test_account_id_.GetUserEmail());
+    return user_context;
+  }
+
+  // Sets up UserSessionManager to use stub authenticator that reports a
+  // password change, and attempts login.
+  // Password changed OOBE dialog is expected to show up after calling this.
+  void SetUpStubAuthentcatorAndAttemptLogin(const std::string& old_password) {
+    UserContext user_context = GetTestUserContext();
+
+    auto authenticator_builder =
+        std::make_unique<StubAuthenticatorBuilder>(user_context);
+    authenticator_builder->SetUpPasswordChange(
+        old_password,
+        base::BindRepeating(&PasswordChangeTest::HandleDataRecoveryStatusChange,
+                            base::Unretained(this)));
+    test::UserSessionManagerTestApi(UserSessionManager::GetInstance())
+        .InjectAuthenticatorBuilder(std::move(authenticator_builder));
+
+    ExistingUserController::current_controller()->SetDisplayEmail(
+        test_account_id_.GetUserEmail());
+    ExistingUserController::current_controller()->Login(user_context,
+                                                        SigninSpecifics());
+  }
+
+ protected:
+  AccountId test_account_id_;
+  StubAuthenticator::DataRecoveryStatus data_recovery_status_ =
+      StubAuthenticator::DataRecoveryStatus::kNone;
+
+ private:
+  void HandleDataRecoveryStatusChange(
+      StubAuthenticator::DataRecoveryStatus status) {
+    EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kNone,
+              data_recovery_status_);
+    data_recovery_status_ = status;
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, PRE_MigrateOldCryptohome) {
+  RegisterUser(test_account_id_);
+  StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, MigrateOldCryptohome) {
+  SetUpStubAuthentcatorAndAttemptLogin("old user password");
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+  OobeUiWindowVisibilityWaiter(true).Wait();
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  // Fill out and submit the old password passed to the stub authenticator.
+  test::OobeJS().TypeIntoPath("old user password",
+                              {"gaia-password-changed", "oldPasswordInput"});
+  test::OobeJS().TapOnPath(
+      {"gaia-password-changed", "oldPasswordInputForm", "button"});
+
+  // User session should start, and whole OOBE screen is expected to be hidden,
+  OobeUiWindowVisibilityWaiter(false).Wait();
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kRecovered,
+            data_recovery_status_);
+
+  SessionStartWaiter().Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, PRE_RetryOnWrongPassword) {
+  RegisterUser(test_account_id_);
+  StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, RetryOnWrongPassword) {
+  SetUpStubAuthentcatorAndAttemptLogin("old user password");
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+  OobeUiWindowVisibilityWaiter(true).Wait();
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  // Fill out and submit the old password passed to the stub authenticator.
+  test::OobeJS().TypeIntoPath("incorrect old user password",
+                              {"gaia-password-changed", "oldPasswordInput"});
+  test::OobeJS().TapOnPath(
+      {"gaia-password-changed", "oldPasswordInputForm", "button"});
+
+  // Expect the UI to report failure.
+  test::OobeJS()
+      .CreateWaiter(test::GetOobeElementPath(
+                        {"gaia-password-changed", "oldPasswordInput"}) +
+                    ".isInvalid")
+      ->Wait();
+  test::OobeJS().ExpectEnabledPath(
+      {"gaia-password-changed", "oldPasswordCard"});
+
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kRecoveryFailed,
+            data_recovery_status_);
+
+  data_recovery_status_ = StubAuthenticator::DataRecoveryStatus::kNone;
+
+  // Submit the correct password.
+  test::OobeJS().TypeIntoPath("old user password",
+                              {"gaia-password-changed", "oldPasswordInput"});
+  test::OobeJS().TapOnPath(
+      {"gaia-password-changed", "oldPasswordInputForm", "button"});
+
+  // User session should start, and whole OOBE screen is expected to be hidden,
+  OobeUiWindowVisibilityWaiter(false).Wait();
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kRecovered,
+            data_recovery_status_);
+
+  SessionStartWaiter().Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, PRE_SkipDataRecovery) {
+  RegisterUser(test_account_id_);
+  StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, SkipDataRecovery) {
+  SetUpStubAuthentcatorAndAttemptLogin("old user password");
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+  OobeUiWindowVisibilityWaiter(true).Wait();
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  // Click forgot password link.
+  test::OobeJS().TapOnPath({"gaia-password-changed", "forgot-password-link"});
+
+  test::OobeJS().CreateVisibilityWaiter(
+      false, {"gaia-password-changed", "oldPasswordCard"});
+
+  test::OobeJS().ExpectVisiblePath({"gaia-password-changed", "try-again-link"});
+  test::OobeJS().ExpectVisiblePath(
+      {"gaia-password-changed", "proceedAnywayBtn"});
+
+  // Click "Proceed anyway".
+  test::OobeJS().TapOnPath({"gaia-password-changed", "proceedAnywayBtn"});
+
+  // User session should start, and whole OOBE screen is expected to be hidden,
+  OobeUiWindowVisibilityWaiter(false).Wait();
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kResynced,
+            data_recovery_status_);
+
+  SessionStartWaiter().Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, PRE_TryAgainAfterForgetLinkClick) {
+  RegisterUser(test_account_id_);
+  StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, TryAgainAfterForgetLinkClick) {
+  SetUpStubAuthentcatorAndAttemptLogin("old user password");
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+  OobeUiWindowVisibilityWaiter(true).Wait();
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  // Click forgot password link.
+  test::OobeJS().TapOnPath({"gaia-password-changed", "forgot-password-link"});
+
+  test::OobeJS().CreateVisibilityWaiter(
+      false, {"gaia-password-changed", "oldPasswordCard"});
+
+  test::OobeJS().ExpectVisiblePath({"gaia-password-changed", "try-again-link"});
+  test::OobeJS().ExpectVisiblePath(
+      {"gaia-password-changed", "proceedAnywayBtn"});
+
+  // Go back to old password input by clicking Try Again.
+  test::OobeJS().TapOnPath({"gaia-password-changed", "try-again-link"});
+
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  // Enter and submit the correct password.
+  test::OobeJS().TypeIntoPath("old user password",
+                              {"gaia-password-changed", "oldPasswordInput"});
+  test::OobeJS().TapOnPath(
+      {"gaia-password-changed", "oldPasswordInputForm", "button"});
+
+  // User session should start, and whole OOBE screen is expected to be hidden,
+  OobeUiWindowVisibilityWaiter(false).Wait();
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kRecovered,
+            data_recovery_status_);
+
+  SessionStartWaiter().Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, PRE_ClosePasswordChangedDialog) {
+  RegisterUser(test_account_id_);
+  StartupUtils::MarkOobeCompleted();
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordChangeTest, ClosePasswordChangedDialog) {
+  SetUpStubAuthentcatorAndAttemptLogin("old user password");
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+  test::OobeJS().CreateVisibilityWaiter(
+      true, {"gaia-password-changed", "oldPasswordCard"});
+
+  test::OobeJS().TypeIntoPath("old user password",
+                              {"gaia-password-changed", "oldPasswordInput"});
+  // Click the close button.
+  test::OobeJS().TapOnPath(
+      {"gaia-password-changed", "navigation", "closeButton"});
+
+  OobeUiWindowVisibilityWaiter(false).Wait();
+  EXPECT_EQ(StubAuthenticator::DataRecoveryStatus::kNone,
+            data_recovery_status_);
+
+  ExistingUserController::current_controller()->Login(GetTestUserContext(),
+                                                      SigninSpecifics());
+  OobeUiWindowVisibilityWaiter(true).Wait();
+  OobeScreenWaiter(OobeScreen::SCREEN_PASSWORD_CHANGED).Wait();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage_unittest.cc b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage_unittest.cc
index 9023448f..2bce1e34 100644
--- a/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage_unittest.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/fingerprint_storage_unittest.cc
@@ -23,7 +23,9 @@
   ~FingerprintStorageUnitTest() override {}
 
   // testing::Test:
-  void SetUp() override { quick_unlock::EnableForTesting(); }
+  void SetUp() override { quick_unlock::EnabledForTesting(true); }
+
+  void TearDown() override { quick_unlock::EnabledForTesting(false); }
 
   void SetRecords(int records_number) {
     profile_->GetPrefs()->SetInteger(prefs::kQuickUnlockFingerprintRecord,
diff --git a/chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs_unittest.cc b/chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs_unittest.cc
index 33b6273..c4951789 100644
--- a/chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs_unittest.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/pin_storage_prefs_unittest.cc
@@ -22,7 +22,9 @@
   ~PinStoragePrefsUnitTest() override = default;
 
   // testing::Test:
-  void SetUp() override { quick_unlock::EnableForTesting(); }
+  void SetUp() override { quick_unlock::EnabledForTesting(true); }
+
+  void TearDown() override { quick_unlock::EnabledForTesting(false); }
 
   quick_unlock::PinStoragePrefs* PinStoragePrefs() const {
     return quick_unlock::QuickUnlockFactory::GetForProfile(profile_.get())
diff --git a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage_unittest.cc b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage_unittest.cc
index 31fafb0..fa9a8d8 100644
--- a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage_unittest.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_storage_unittest.cc
@@ -43,7 +43,8 @@
   ~QuickUnlockStorageUnitTest() override {}
 
   // testing::Test:
-  void SetUp() override { quick_unlock::EnableForTesting(); }
+  void SetUp() override { quick_unlock::EnabledForTesting(true); }
+  void TearDown() override { quick_unlock::EnabledForTesting(false); }
 
   void ExpireAuthToken() {
     quick_unlock::QuickUnlockFactory::GetForProfile(profile_.get())
diff --git a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
index 5c3bea51..7794988 100644
--- a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
@@ -151,8 +151,8 @@
   return base::FeatureList::IsEnabled(features::kQuickUnlockFingerprint);
 }
 
-void EnableForTesting() {
-  enable_for_testing_ = true;
+void EnabledForTesting(bool state) {
+  enable_for_testing_ = state;
 }
 
 bool IsEnabledForTesting() {
diff --git a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
index d4c15ba..ffa95296 100644
--- a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
+++ b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
@@ -48,8 +48,8 @@
 // by cros_config.
 bool IsFingerprintReaderOnKeyboard();
 
-// Forcibly enable all quick-unlock modes for testing.
-void EnableForTesting();
+// Enable or Disable quick-unlock modes for testing
+void EnabledForTesting(bool state);
 
 // Returns true if EnableForTesting() was previously called.
 bool IsEnabledForTesting();
diff --git a/chrome/browser/chromeos/login/reset_browsertest.cc b/chrome/browser/chromeos/login/reset_browsertest.cc
index c7a387e4..aafeb36 100644
--- a/chrome/browser/chromeos/login/reset_browsertest.cc
+++ b/chrome/browser/chromeos/login/reset_browsertest.cc
@@ -6,12 +6,13 @@
 
 #include "base/command_line.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/login_wizard.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/screens/reset_screen.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_exit_waiter.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -37,11 +38,9 @@
 
 }  // namespace
 
-class ResetTest : public LoginManagerTest {
+class ResetTest : public MixinBasedInProcessBrowserTest {
  public:
-  ResetTest() : LoginManagerTest(false, false /* should_initialize_webui */) {
-    set_force_webui_login(false);
-  }
+  ResetTest() = default;
   ~ResetTest() override = default;
 
   // LoginManagerTest overrides:
@@ -52,12 +51,7 @@
     dbus_setter->SetUpdateEngineClient(
         std::unique_ptr<UpdateEngineClient>(update_engine_client_));
 
-    LoginManagerTest::SetUpInProcessBrowserTestFixture();
-  }
-
-  void RegisterSomeUser() {
-    RegisterUser(AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId));
-    StartupUtils::MarkOobeCompleted();
+    MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
   }
 
   void InvokeResetScreen() {
@@ -100,6 +94,9 @@
   FakeUpdateEngineClient* update_engine_client_ = nullptr;
 
  private:
+  LoginManagerMixin login_manager_mixin_{
+      &mixin_host_,
+      {AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId)}};
   DISALLOW_COPY_AND_ASSIGN(ResetTest);
 };
 
@@ -179,10 +176,6 @@
       tpm_firmware_update_checker_callback_;
 };
 
-IN_PROC_BROWSER_TEST_F(ResetTest, PRE_ShowAndCancel) {
-  RegisterSomeUser();
-}
-
 IN_PROC_BROWSER_TEST_F(ResetTest, ShowAndCancel) {
   InvokeResetScreen();
   test::OobeJS().ExpectVisible("reset");
@@ -191,10 +184,6 @@
   test::OobeJS().CreateVisibilityWaiter(false, {"reset"});
 }
 
-IN_PROC_BROWSER_TEST_F(ResetTest, PRE_RestartBeforePowerwash) {
-  RegisterSomeUser();
-}
-
 IN_PROC_BROWSER_TEST_F(ResetTest, RestartBeforePowerwash) {
   PrefService* prefs = g_browser_process->local_state();
 
@@ -211,7 +200,6 @@
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, PRE_ViewsLogic) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
   update_engine_client_->set_can_rollback_check_result(false);
 }
 
@@ -256,7 +244,6 @@
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, PRE_ShowAfterBootIfRequested) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, ShowAfterBootIfRequested) {
@@ -269,7 +256,6 @@
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, PRE_RollbackUnavailable) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, RollbackUnavailable) {
@@ -302,7 +288,6 @@
                        PRE_RollbackAvailable) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback, RollbackAvailable) {
@@ -351,7 +336,6 @@
                        PRE_ErrorOnRollbackRequested) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback,
@@ -378,7 +362,6 @@
                        PRE_RevertAfterCancel) {
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
-  RegisterSomeUser();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback, RevertAfterCancel) {
@@ -408,11 +391,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
-                       PRE_PRE_ResetFromSigninWithFirmwareUpdate) {
-  RegisterSomeUser();
-}
-
-IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_ResetFromSigninWithFirmwareUpdate) {
   InvokeResetScreen();
   test::OobeJS().ExpectHiddenPath({"oobe-reset-md", "tpmFirmwareUpdate"});
@@ -456,7 +434,6 @@
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_TpmFirmwareUpdateAvailableButNotSelected) {
-  RegisterSomeUser();
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
 }
@@ -483,7 +460,6 @@
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_ResetWithTpmCleanUp) {
-  RegisterSomeUser();
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
   prefs->SetInteger(prefs::kFactoryResetTPMFirmwareUpdateMode,
@@ -517,7 +493,6 @@
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_ResetWithTpmUpdatePreservingDeviceState) {
-  RegisterSomeUser();
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
   prefs->SetInteger(
@@ -555,7 +530,6 @@
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_TpmFirmwareUpdateRequestedBeforeShowNotEditable) {
-  RegisterSomeUser();
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
   prefs->SetInteger(prefs::kFactoryResetTPMFirmwareUpdateMode,
@@ -599,7 +573,6 @@
 
 IN_PROC_BROWSER_TEST_F(ResetTestWithTpmFirmwareUpdate,
                        PRE_AvailableTpmUpdateModesChangeDuringRequest) {
-  RegisterSomeUser();
   PrefService* prefs = g_browser_process->local_state();
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
   prefs->SetInteger(prefs::kFactoryResetTPMFirmwareUpdateMode,
diff --git a/chrome/browser/chromeos/login/screens/fingerprint_setup_browsertest.cc b/chrome/browser/chromeos/login/screens/fingerprint_setup_browsertest.cc
new file mode 100644
index 0000000..c40c48f
--- /dev/null
+++ b/chrome/browser/chromeos/login/screens/fingerprint_setup_browsertest.cc
@@ -0,0 +1,208 @@
+// 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/login/login_wizard.h"
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
+#include "chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h"
+#include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
+#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
+#include "chromeos/dbus/biod/fake_biod_client.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr char kTestFingerprintDataString[] = "testFinger";
+
+int kMaxAllowedFingerprints = 3;
+
+chromeos::OobeUI* GetOobeUI() {
+  auto* host = chromeos::LoginDisplayHost::default_host();
+  return host ? host->GetOobeUI() : nullptr;
+}
+
+}  // namespace
+
+class FingerprintSetupTest : public InProcessBrowserTest {
+ public:
+  FingerprintSetupTest() = default;
+  ~FingerprintSetupTest() override = default;
+
+  void SetUpOnMainThread() override {
+    ShowLoginWizard(OobeScreen::SCREEN_TEST_NO_WINDOW);
+
+    fingerprint_setup_screen_ = std::make_unique<FingerprintSetupScreen>(
+        GetOobeUI()->GetFingerprintSetupScreenView(),
+        base::BindRepeating(&FingerprintSetupTest::OnFingerprintSetupScreenExit,
+                            base::Unretained(this)));
+
+    InProcessBrowserTest::SetUpOnMainThread();
+  }
+
+  void TearDownOnMainThread() override {
+    fingerprint_setup_screen_.reset();
+
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  void WaitForScreenExit() {
+    if (screen_exit_)
+      return;
+    base::RunLoop run_loop;
+    screen_exit_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  void OnFingerprintSetupScreenExit() {
+    screen_exit_ = true;
+    if (screen_exit_callback_) {
+      std::move(screen_exit_callback_).Run();
+    }
+  }
+
+  void EnrollFingerprint(int percent_complete) {
+    base::RunLoop().RunUntilIdle();
+    FakeBiodClient::Get()->SendEnrollScanDone(
+        kTestFingerprintDataString, biod::SCAN_RESULT_SUCCESS,
+        percent_complete == 100 /* is_complete */, percent_complete);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void CheckCompletedEnroll() {
+    test::OobeJS().ExpectVisiblePath({"fingerprint-setup-impl", "arc"});
+    test::OobeJS()
+        .CreateVisibilityWaiter(
+            true, {"fingerprint-setup-impl", "fingerprintEnrollDone"})
+        ->Wait();
+    test::OobeJS().ExpectHiddenPath(
+        {"fingerprint-setup-impl", "skipFingerprintEnroll"});
+    test::OobeJS().ExpectVisiblePath(
+        {"fingerprint-setup-impl", "arc", "checkmarkAnimation"});
+    test::OobeJS().ExpectVisiblePath(
+        {"fingerprint-setup-impl", "fingerprintAddAnother"});
+  }
+
+  std::unique_ptr<FingerprintSetupScreen> fingerprint_setup_screen_;
+
+ private:
+  bool screen_exit_ = false;
+
+  base::OnceClosure screen_exit_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintEnrollHalf) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+
+  EnrollFingerprint(50);
+  test::OobeJS().ExpectVisiblePath({"fingerprint-setup-impl", "arc"});
+  test::OobeJS().ExpectVisiblePath(
+      {"fingerprint-setup-impl", "skipFingerprintEnroll"});
+  test::OobeJS().ExpectHiddenPath(
+      {"fingerprint-setup-impl", "fingerprintAddAnother"});
+  test::OobeJS().ExpectHiddenPath(
+      {"fingerprint-setup-impl", "fingerprintEnrollDone"});
+
+  test::OobeJS().TapOnPath({"fingerprint-setup-impl", "skipFingerprintEnroll"});
+
+  WaitForScreenExit();
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintEnrollFull) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+  EnrollFingerprint(100);
+  CheckCompletedEnroll();
+
+  test::OobeJS().TapOnPath({"fingerprint-setup-impl", "fingerprintEnrollDone"});
+
+  WaitForScreenExit();
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintEnrollLimit) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+
+  for (int i = 0; i < kMaxAllowedFingerprints - 1; i++) {
+    EnrollFingerprint(100);
+    CheckCompletedEnroll();
+    test::OobeJS().TapOnPath(
+        {"fingerprint-setup-impl", "fingerprintAddAnother", "textButton"});
+  }
+
+  EnrollFingerprint(100);
+  test::OobeJS().ExpectHiddenPath(
+      {"fingerprint-setup-impl", "fingerprintAddAnother"});
+  test::OobeJS().TapOnPath({"fingerprint-setup-impl", "fingerprintEnrollDone"});
+
+  WaitForScreenExit();
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintDisabled) {
+  quick_unlock::EnabledForTesting(false);
+  fingerprint_setup_screen_->Show();
+
+  WaitForScreenExit();
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintSetupScreenElements) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+
+  test::OobeJS().CreateVisibilityWaiter(true, {"fingerprint-setup"})->Wait();
+  test::OobeJS().ExpectVisible("fingerprint-setup-impl");
+
+  test::OobeJS().ExpectVisiblePath(
+      {"fingerprint-setup-impl", "setupFingerprint"});
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintSetupCancel) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+  test::OobeJS().TapOnPath({"fingerprint-setup-impl", "skipFingerprintSetup"});
+  WaitForScreenExit();
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintSetupNext) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+
+  test::OobeJS().CreateVisibilityWaiter(true, {"fingerprint-setup"})->Wait();
+  test::OobeJS().TapOnPath(
+      {"fingerprint-setup-impl", "showSensorLocationButton"});
+  test::OobeJS()
+      .CreateVisibilityWaiter(true, {"fingerprint-setup-impl", "placeFinger"})
+      ->Wait();
+  test::OobeJS().ExpectHiddenPath(
+      {"fingerprint-setup-impl", "setupFingerprint"});
+}
+
+IN_PROC_BROWSER_TEST_F(FingerprintSetupTest, FingerprintSetupLater) {
+  quick_unlock::EnabledForTesting(true);
+  fingerprint_setup_screen_->Show();
+  OobeScreenWaiter(OobeScreen::SCREEN_FINGERPRINT_SETUP).Wait();
+
+  test::OobeJS().CreateVisibilityWaiter(true, {"fingerprint-setup"})->Wait();
+  test::OobeJS().TapOnPath(
+      {"fingerprint-setup-impl", "showSensorLocationButton"});
+  test::OobeJS()
+      .CreateVisibilityWaiter(
+          true, {"fingerprint-setup-impl", "setupFingerprintLater"})
+      ->Wait();
+  test::OobeJS().TapOnPath({"fingerprint-setup-impl", "setupFingerprintLater"});
+
+  WaitForScreenExit();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
index 2a4587c..9c40de71 100644
--- a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
+++ b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
@@ -3,8 +3,9 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h"
-
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/chromeos/login/users/chrome_user_manager_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
 
 namespace chromeos {
 namespace {
@@ -28,7 +29,9 @@
 }
 
 void FingerprintSetupScreen::Show() {
-  if (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) {
+  if (!chromeos::quick_unlock::IsFingerprintEnabled(
+          ProfileManager::GetActiveUserProfile()) ||
+      chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) {
     exit_callback_.Run();
     return;
   }
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index ec87b0d..c9bb2ea 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -117,7 +117,7 @@
 #include "chromeos/dbus/cryptohome/tpm_util.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
-#include "chromeos/login/auth/stub_authenticator.h"
+#include "chromeos/login/auth/stub_authenticator_builder.h"
 #include "chromeos/network/network_cert_loader.h"
 #include "chromeos/network/portal_detector/network_portal_detector.h"
 #include "chromeos/network/portal_detector/network_portal_detector_strategy.h"
@@ -588,9 +588,8 @@
   }
 
   if (authenticator_.get() == NULL) {
-    if (injected_user_context_) {
-      authenticator_ =
-          new StubAuthenticator(consumer, *injected_user_context_.get());
+    if (injected_authenticator_builder_) {
+      authenticator_ = injected_authenticator_builder_->Create(consumer);
     } else {
       authenticator_ = new ChromeCryptohomeAuthenticator(consumer);
     }
@@ -2227,9 +2226,9 @@
   default_ime_states_.erase(profile);
 }
 
-void UserSessionManager::InjectStubUserContext(
-    const UserContext& user_context) {
-  injected_user_context_.reset(new UserContext(user_context));
+void UserSessionManager::InjectAuthenticatorBuilder(
+    std::unique_ptr<StubAuthenticatorBuilder> builder) {
+  injected_authenticator_builder_ = std::move(builder);
   authenticator_ = NULL;
 }
 
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.h b/chrome/browser/chromeos/login/session/user_session_manager.h
index ba445358..60d83ce 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.h
+++ b/chrome/browser/chromeos/login/session/user_session_manager.h
@@ -61,6 +61,7 @@
 class EasyUnlockKeyManager;
 class InputEventsBlocker;
 class LoginDisplayHost;
+class StubAuthenticatorBuilder;
 
 class UserSessionManagerDelegate {
  public:
@@ -479,10 +480,8 @@
   void CreateTokenUtilIfMissing();
 
   // Test API methods.
-
-  // Injects |user_context| that will be used to create StubAuthenticator
-  // instance when CreateAuthenticator() is called.
-  void InjectStubUserContext(const UserContext& user_context);
+  void InjectAuthenticatorBuilder(
+      std::unique_ptr<StubAuthenticatorBuilder> builer);
 
   // Controls whether browser instance should be launched after sign in
   // (used in tests).
@@ -526,8 +525,7 @@
   scoped_refptr<Authenticator> authenticator_;
   StartSessionType start_session_type_;
 
-  // Injected user context for stub authenticator.
-  std::unique_ptr<UserContext> injected_user_context_;
+  std::unique_ptr<StubAuthenticatorBuilder> injected_authenticator_builder_;
 
   // True if the authentication context's cookie jar contains authentication
   // cookies from the authentication extension login flow.
diff --git a/chrome/browser/chromeos/login/session/user_session_manager_test_api.cc b/chrome/browser/chromeos/login/session/user_session_manager_test_api.cc
index e25708aa..fb7e977c 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager_test_api.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager_test_api.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/chromeos/login/session/user_session_manager_test_api.h"
 
+#include "chromeos/login/auth/stub_authenticator_builder.h"
+
 namespace chromeos {
 namespace test {
 
@@ -13,7 +15,13 @@
 
 void UserSessionManagerTestApi::InjectStubUserContext(
     const UserContext& user_context) {
-  session_manager_->InjectStubUserContext(user_context);
+  session_manager_->InjectAuthenticatorBuilder(
+      std::make_unique<StubAuthenticatorBuilder>(user_context));
+}
+
+void UserSessionManagerTestApi::InjectAuthenticatorBuilder(
+    std::unique_ptr<StubAuthenticatorBuilder> builder) {
+  session_manager_->InjectAuthenticatorBuilder(std::move(builder));
 }
 
 void UserSessionManagerTestApi::SetShouldLaunchBrowserInTests(
diff --git a/chrome/browser/chromeos/login/session/user_session_manager_test_api.h b/chrome/browser/chromeos/login/session/user_session_manager_test_api.h
index bbe43d7..9faeea4b 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager_test_api.h
+++ b/chrome/browser/chromeos/login/session/user_session_manager_test_api.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SESSION_USER_SESSION_MANAGER_TEST_API_H_
 #define CHROME_BROWSER_CHROMEOS_LOGIN_SESSION_USER_SESSION_MANAGER_TEST_API_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
 
@@ -18,8 +20,12 @@
 
   // Injects |user_context| that will be used to create StubAuthenticator
   // instance when UserSessionManager::CreateAuthenticator() is called.
+  // DEPRECATED: Use InjectStubAuthenticatorBuilder instead.
   void InjectStubUserContext(const UserContext& user_context);
 
+  void InjectAuthenticatorBuilder(
+      std::unique_ptr<StubAuthenticatorBuilder> builder);
+
   // Controls whether browser instance should be launched after sign in
   // (used in tests).
   void SetShouldLaunchBrowserInTests(bool should_launch_browser);
diff --git a/chrome/browser/chromeos/login/test/active_directory_login_mixin.cc b/chrome/browser/chromeos/login/test/active_directory_login_mixin.cc
new file mode 100644
index 0000000..a10b488
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/active_directory_login_mixin.cc
@@ -0,0 +1,252 @@
+// 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/login/test/active_directory_login_mixin.h"
+
+#include "chrome/browser/chromeos/login/login_shelf_test_helper.h"
+#include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
+#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
+#include "chromeos/dbus/auth_policy/fake_auth_policy_client.h"
+#include "chromeos/tpm/stub_install_attributes.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace chromeos {
+
+namespace {
+
+constexpr char kGaiaSigninId[] = "signin-frame-dialog";
+constexpr char kAdOfflineAuthId[] = "offline-ad-auth";
+
+constexpr char kAdMachineInput[] = "machineNameInput";
+constexpr char kAdMoreOptionsButton[] = "moreOptionsBtn";
+constexpr char kAdUserInput[] = "userInput";
+constexpr char kAdPasswordInput[] = "passwordInput";
+constexpr char kAdCredsButton[] = "nextButton";
+constexpr char kAdAutocompleteRealm[] = "$.userInput.querySelector('span')";
+
+constexpr char kPasswordChangeId[] = "active-directory-password-change";
+constexpr char kAdAnimatedPages[] = "animatedPages";
+constexpr char kAdOldPasswordInput[] = "oldPassword";
+constexpr char kAdNewPassword1Input[] = "newPassword1";
+constexpr char kAdNewPassword2Input[] = "newPassword2";
+constexpr char kPasswordChangeFormId[] = "inputForm";
+constexpr char kFormButtonId[] = "button";
+
+constexpr char kNavigationId[] = "navigation";
+constexpr char kCloseButtonId[] = "closeButton";
+
+}  // namespace
+
+ActiveDirectoryLoginMixin::ActiveDirectoryLoginMixin(
+    InProcessBrowserTestMixinHost* host,
+    const std::string& realm)
+    : InProcessBrowserTestMixin(host),
+      install_attributes_(
+          chromeos::StubInstallAttributes::CreateActiveDirectoryManaged(
+              realm,
+              "device_id")) {}
+
+void ActiveDirectoryLoginMixin::SetUpInProcessBrowserTestFixture() {
+  AuthPolicyClient::InitializeFake();
+  FakeAuthPolicyClient::Get()->DisableOperationDelayForTesting();
+}
+
+void ActiveDirectoryLoginMixin::SetUpOnMainThread() {
+  // Set the threshold to a max value to disable the offline message screen on
+  // slow configurations like MSAN, where it otherwise triggers on every run.
+  LoginDisplayHost::default_host()
+      ->GetOobeUI()
+      ->signin_screen_handler()
+      ->SetOfflineTimeoutForTesting(base::TimeDelta::Max());
+
+  message_queue_ = std::make_unique<content::DOMMessageQueue>();
+  SetupActiveDirectoryJSNotifications();
+}
+
+void ActiveDirectoryLoginMixin::TriggerPasswordChangeScreen() {
+  OobeScreenWaiter screen_waiter(
+      OobeScreen::SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE);
+
+  FakeAuthPolicyClient::Get()->set_auth_error(
+      authpolicy::ERROR_PASSWORD_EXPIRED);
+  SubmitActiveDirectoryCredentials("any_user@any_realm", "any_password");
+  screen_waiter.Wait();
+  TestPasswordChangeError(std::string());
+}
+
+void ActiveDirectoryLoginMixin::ClosePasswordChangeScreen() {
+  test::OobeJS().TapOnPath({kPasswordChangeId, kNavigationId, kCloseButtonId});
+}
+
+void ActiveDirectoryLoginMixin::ExpectValid(const std::string& parent_id,
+                                            const std::string& child_id,
+                                            bool valid) {
+  std::string js = test::GetOobeElementPath({parent_id, child_id}) + ".invalid";
+  if (valid)
+    test::OobeJS().ExpectFalse(js);
+  else
+    test::OobeJS().ExpectTrue(js);
+}
+
+// Checks if Active Directory login is visible.
+void ActiveDirectoryLoginMixin::TestLoginVisible() {
+  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_GAIA_SIGNIN);
+  screen_waiter.Wait();
+  // Checks if Gaia signin is hidden.
+  test::OobeJS().ExpectHidden(kGaiaSigninId);
+
+  // Checks if Active Directory signin is visible.
+  test::OobeJS().ExpectVisible(kAdOfflineAuthId);
+  test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMachineInput});
+  test::OobeJS().ExpectHiddenPath({kAdOfflineAuthId, kAdMoreOptionsButton});
+  test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdUserInput});
+  test::OobeJS().ExpectVisiblePath({kAdOfflineAuthId, kAdPasswordInput});
+
+  test::OobeJS().ExpectEQ(
+      JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".innerText.trim()",
+      autocomplete_realm_);
+
+  EXPECT_TRUE(LoginShelfTestHelper().IsLoginShelfShown());
+}
+
+// Checks if Active Directory password change screen is shown.
+void ActiveDirectoryLoginMixin::TestPasswordChangeVisible() {
+  // Checks if Gaia signin is hidden.
+  test::OobeJS().ExpectHidden(kGaiaSigninId);
+  // Checks if Active Directory signin is visible.
+  test::OobeJS().ExpectVisible(kPasswordChangeId);
+  test::OobeJS().ExpectTrue(
+      test::GetOobeElementPath({kPasswordChangeId, kAdAnimatedPages}) +
+      ".selected == 0");
+  test::OobeJS().ExpectVisiblePath(
+      {kPasswordChangeId, kNavigationId, kCloseButtonId});
+}
+
+// Checks if user input is marked as invalid.
+void ActiveDirectoryLoginMixin::TestUserError() {
+  TestLoginVisible();
+  ExpectValid(kAdOfflineAuthId, kAdUserInput, false);
+}
+
+void ActiveDirectoryLoginMixin::SetUserInput(const std::string& value) {
+  test::OobeJS().TypeIntoPath(value, {kAdOfflineAuthId, kAdUserInput});
+}
+
+void ActiveDirectoryLoginMixin::TestUserInput(const std::string& value) {
+  test::OobeJS().ExpectEQ(
+      test::GetOobeElementPath({kAdOfflineAuthId, kAdUserInput}) + ".value",
+      value);
+}
+
+// Checks if password input is marked as invalid.
+void ActiveDirectoryLoginMixin::TestPasswordError() {
+  TestLoginVisible();
+  ExpectValid(kAdOfflineAuthId, kAdPasswordInput, false);
+}
+
+// Checks that machine, password and user inputs are valid.
+void ActiveDirectoryLoginMixin::TestNoError() {
+  TestLoginVisible();
+  ExpectValid(kAdOfflineAuthId, kAdMachineInput, true);
+  ExpectValid(kAdOfflineAuthId, kAdUserInput, true);
+  ExpectValid(kAdOfflineAuthId, kAdPasswordInput, true);
+}
+
+// Checks if autocomplete domain is visible for the user input.
+void ActiveDirectoryLoginMixin::TestDomainVisible() {
+  test::OobeJS().ExpectTrue(
+      "!" + JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) + ".hidden");
+}
+
+// Checks if autocomplete domain is hidden for the user input.
+void ActiveDirectoryLoginMixin::TestDomainHidden() {
+  test::OobeJS().ExpectTrue(JSElement(kAdOfflineAuthId, kAdAutocompleteRealm) +
+                            ".hidden");
+}
+
+void ActiveDirectoryLoginMixin::TestPasswordChangeNoErrors() {
+  TestPasswordChangeError("");
+}
+
+void ActiveDirectoryLoginMixin::TestPasswordChangeOldPasswordError() {
+  TestPasswordChangeError(kAdOldPasswordInput);
+}
+
+void ActiveDirectoryLoginMixin::TestPasswordChangeNewPasswordError() {
+  TestPasswordChangeError(kAdNewPassword1Input);
+}
+
+void ActiveDirectoryLoginMixin::TestPasswordChangeConfirmNewPasswordError() {
+  TestPasswordChangeError(kAdNewPassword2Input);
+}
+
+// Checks if Active Directory password change screen is shown. Also checks if
+// |invalid_element| is invalidated and all the other elements are valid.
+void ActiveDirectoryLoginMixin::TestPasswordChangeError(
+    const std::string& invalid_element) {
+  TestPasswordChangeVisible();
+  for (const char* element :
+       {kAdOldPasswordInput, kAdNewPassword1Input, kAdNewPassword2Input}) {
+    std::string js_assertion =
+        test::GetOobeElementPath({kPasswordChangeId, element}) + ".isInvalid";
+    if (element != invalid_element)
+      js_assertion = "!" + js_assertion;
+    test::OobeJS().ExpectTrue(js_assertion);
+  }
+}
+
+// Sets username and password for the Active Directory login and submits it.
+void ActiveDirectoryLoginMixin::SubmitActiveDirectoryCredentials(
+    const std::string& username,
+    const std::string& password) {
+  test::OobeJS().TypeIntoPath(username, {kAdOfflineAuthId, kAdUserInput});
+  test::OobeJS().TypeIntoPath(password, {kAdOfflineAuthId, kAdPasswordInput});
+  test::OobeJS().TapOnPath({kAdOfflineAuthId, kAdCredsButton});
+}
+
+// Sets username and password for the Active Directory login and submits it.
+void ActiveDirectoryLoginMixin::SubmitActiveDirectoryPasswordChangeCredentials(
+    const std::string& old_password,
+    const std::string& new_password1,
+    const std::string& new_password2) {
+  test::OobeJS().TypeIntoPath(old_password,
+                              {kPasswordChangeId, kAdOldPasswordInput});
+  test::OobeJS().TypeIntoPath(new_password1,
+                              {kPasswordChangeId, kAdNewPassword1Input});
+  test::OobeJS().TypeIntoPath(new_password2,
+                              {kPasswordChangeId, kAdNewPassword2Input});
+  test::OobeJS().TapOnPath(
+      {kPasswordChangeId, kPasswordChangeFormId, kFormButtonId});
+}
+
+void ActiveDirectoryLoginMixin::SetupActiveDirectoryJSNotifications() {
+  test::OobeJS().Evaluate(
+      "var testInvalidateAd = login.GaiaSigninScreen.invalidateAd;"
+      "login.GaiaSigninScreen.invalidateAd = function(user, errorState) {"
+      "  testInvalidateAd(user, errorState);"
+      "  window.domAutomationController.send('ShowAuthError');"
+      "}");
+}
+
+void ActiveDirectoryLoginMixin::WaitForAuthError() {
+  const std::string& expected_message = "\"ShowAuthError\"";
+  std::string message;
+  do {
+    ASSERT_TRUE(message_queue_->WaitForMessage(&message));
+  } while (message != expected_message);
+}
+
+// Returns string representing element with id=|element_id| inside Active
+// Directory login element.
+std::string ActiveDirectoryLoginMixin::JSElement(const std::string& parent_id,
+                                                 const std::string& selector) {
+  return "document.querySelector('#" + parent_id + "')." + selector;
+}
+
+ActiveDirectoryLoginMixin::~ActiveDirectoryLoginMixin() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/active_directory_login_mixin.h b/chrome/browser/chromeos/login/test/active_directory_login_mixin.h
new file mode 100644
index 0000000..10a4f5b7
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/active_directory_login_mixin.h
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_TEST_ACTIVE_DIRECTORY_LOGIN_MIXIN_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_TEST_ACTIVE_DIRECTORY_LOGIN_MIXIN_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
+#include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.h"
+#include "chrome/browser/policy/test/local_policy_test_server.h"
+#include "chromeos/tpm/stub_install_attributes.h"
+#include "components/policy/proto/chrome_device_policy.pb.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace chromeos {
+
+// Handles interaction with Active Directory login screen and Active Directory
+// password change screen.
+class ActiveDirectoryLoginMixin : public InProcessBrowserTestMixin {
+ public:
+  explicit ActiveDirectoryLoginMixin(InProcessBrowserTestMixinHost* host,
+                                     const std::string& realm);
+  ~ActiveDirectoryLoginMixin() override;
+
+  // InProcessBrowserTestMixin:
+  void SetUpInProcessBrowserTestFixture() override;
+  void SetUpOnMainThread() override;
+
+  void set_autocomplete_realm(const std::string& autocomplete_realm) {
+    autocomplete_realm_ = autocomplete_realm;
+  }
+
+  // Checks if Active Directory login is visible.
+  void TestLoginVisible();
+  // Checks if Active Directory password change screen is shown.
+  void TestPasswordChangeVisible();
+  // Checks if user input is marked as invalid.
+  void TestUserError();
+  void SetUserInput(const std::string& value);
+  void TestUserInput(const std::string& value);
+  // Checks if password input is marked as invalid.
+  void TestPasswordError();
+  // Checks that machine, password and user inputs are valid.
+  void TestNoError();
+  // Checks if autocomplete domain is visible for the user input.
+  void TestDomainVisible();
+  // Checks if autocomplete domain is hidden for the user input.
+  void TestDomainHidden();
+
+  void TriggerPasswordChangeScreen();
+  void ClosePasswordChangeScreen();
+  // Checks if Active Directory password change screen is shown. Also checks if
+  // |invalid_element| is invalidated and all the other elements are valid.
+  void TestPasswordChangeNoErrors();
+  void TestPasswordChangeOldPasswordError();
+  void TestPasswordChangeNewPasswordError();
+  void TestPasswordChangeConfirmNewPasswordError();
+
+  // Sets username and password for the Active Directory login and submits it.
+  void SubmitActiveDirectoryCredentials(const std::string& username,
+                                        const std::string& password);
+
+  // Sets username and password for the Active Directory login and submits it.
+  void SubmitActiveDirectoryPasswordChangeCredentials(
+      const std::string& old_password,
+      const std::string& new_password1,
+      const std::string& new_password2);
+
+  // Waits when Active Directory screen been invalidated from inside Chrome.
+  void WaitForAuthError();
+
+ private:
+  void SetupActiveDirectoryJSNotifications();
+  void TestPasswordChangeError(const std::string& invalid_element);
+  void ExpectValid(const std::string& parent_id,
+                   const std::string& child_id,
+                   bool valid);
+  // Returns string representing element with id=|element_id| inside Active
+  // Directory login element.
+  std::string JSElement(const std::string& parent_id,
+                        const std::string& selector);
+
+  std::string autocomplete_realm_;
+  std::unique_ptr<content::DOMMessageQueue> message_queue_;
+  chromeos::ScopedStubInstallAttributes install_attributes_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActiveDirectoryLoginMixin);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_TEST_ACTIVE_DIRECTORY_LOGIN_MIXIN_H_
diff --git a/chrome/browser/chromeos/login/test/login_manager_mixin.cc b/chrome/browser/chromeos/login/test/login_manager_mixin.cc
new file mode 100644
index 0000000..4c8b8a652
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/login_manager_mixin.cc
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
+
+#include <memory>
+#include <string>
+
+#include "base/command_line.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_browser_main.h"
+#include "chrome/browser/chrome_browser_main_extra_parts.h"
+#include "chrome/browser/chromeos/login/session/user_session_manager.h"
+#include "chrome/browser/chromeos/login/session/user_session_manager_test_api.h"
+#include "chrome/browser/chromeos/login/startup_utils.h"
+#include "chromeos/constants/chromeos_switches.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/user_manager/fake_user_manager.h"
+#include "components/user_manager/known_user.h"
+#include "components/user_manager/scoped_user_manager.h"
+
+namespace chromeos {
+
+namespace {
+
+// Chrome main extra part used for login manager tests to set up initially
+// registered users. The main part injects itself into browser startup after
+// local state has been set up, but before the user manager instance is created.
+// It adds list of "known" users to the local state, so user manager can load
+// them during its initialization.
+// When users are registered, it marks OOBE as complete.
+class TestUserRegistrationMainExtra : public ChromeBrowserMainExtraParts {
+ public:
+  explicit TestUserRegistrationMainExtra(const std::vector<AccountId>& users)
+      : users_(users) {}
+  ~TestUserRegistrationMainExtra() override = default;
+
+  // ChromeBrowserMainExtraParts:
+  void PostEarlyInitialization() override {
+    // SaveKnownUser depends on UserManager to get the local state that has to
+    // be updated, and do ephemeral user checks.
+    // Given that user manager does not exist yet (by design), create a
+    // temporary fake user manager instance.
+    {
+      auto user_manager = std::make_unique<user_manager::FakeUserManager>();
+      user_manager->set_local_state(g_browser_process->local_state());
+      user_manager::ScopedUserManager scoper(std::move(user_manager));
+      for (const auto& account_id : users_) {
+        ListPrefUpdate users_pref(g_browser_process->local_state(),
+                                  "LoggedInUsers");
+        users_pref->AppendIfNotPresent(
+            std::make_unique<base::Value>(account_id.GetUserEmail()));
+
+        user_manager::known_user::UpdateId(account_id);
+      }
+    }
+
+    StartupUtils::MarkOobeCompleted();
+  }
+
+ private:
+  const std::vector<AccountId> users_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUserRegistrationMainExtra);
+};
+
+}  // namespace
+
+LoginManagerMixin::LoginManagerMixin(
+    InProcessBrowserTestMixinHost* host,
+    const std::vector<AccountId>& initial_users)
+    : InProcessBrowserTestMixin(host), initial_users_(initial_users) {}
+
+LoginManagerMixin::~LoginManagerMixin() = default;
+
+void LoginManagerMixin::SetUpCommandLine(base::CommandLine* command_line) {
+  command_line->AppendSwitch(chromeos::switches::kLoginManager);
+  command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
+}
+
+void LoginManagerMixin::CreatedBrowserMainParts(
+    content::BrowserMainParts* browser_main_parts) {
+  // |browser_main_parts| take ownership of TestUserRegistrationMainExtra.
+  static_cast<ChromeBrowserMainParts*>(browser_main_parts)
+      ->AddParts(new TestUserRegistrationMainExtra(initial_users_));
+}
+
+void LoginManagerMixin::SetUpOnMainThread() {
+  test::UserSessionManagerTestApi session_manager_test_api(
+      UserSessionManager::GetInstance());
+  session_manager_test_api.SetShouldLaunchBrowserInTests(false);
+  session_manager_test_api.SetShouldObtainTokenHandleInTests(false);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/login_manager_mixin.h b/chrome/browser/chromeos/login/test/login_manager_mixin.h
new file mode 100644
index 0000000..35c50b8
--- /dev/null
+++ b/chrome/browser/chromeos/login/test/login_manager_mixin.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_MANAGER_MIXIN_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_MANAGER_MIXIN_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
+#include "components/account_id/account_id.h"
+
+namespace chromeos {
+
+// Mixin browser tests can use for setting up test login manager environment.
+// It sets up command line so test starts on the login screen UI, and
+// initializes user manager with a list of pre-registered users.
+// The mixin will mark the OOBE flow as complete during test setup, so it's not
+// suitable for OOBE tests.
+class LoginManagerMixin : public InProcessBrowserTestMixin {
+ public:
+  LoginManagerMixin(InProcessBrowserTestMixinHost* host,
+                    const std::vector<AccountId>& initial_users);
+  ~LoginManagerMixin() override;
+
+  // InProcessBrowserTestMixin:
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+  void CreatedBrowserMainParts(
+      content::BrowserMainParts* browser_main_parts) override;
+  void SetUpOnMainThread() override;
+
+ private:
+  const std::vector<AccountId> initial_users_;
+
+  DISALLOW_COPY_AND_ASSIGN(LoginManagerMixin);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_TEST_LOGIN_MANAGER_MIXIN_H_
diff --git a/chrome/browser/chromeos/login/ui/login_display.h b/chrome/browser/chromeos/login/ui/login_display.h
index 11defe7..1fbaf37d 100644
--- a/chrome/browser/chromeos/login/ui/login_display.h
+++ b/chrome/browser/chromeos/login/ui/login_display.h
@@ -121,9 +121,6 @@
   // signin but whitelist check fails.
   virtual void ShowWhitelistCheckFailedError() = 0;
 
-  // Show unrecoverable cryptohome error dialog.
-  virtual void ShowUnrecoverableCrypthomeErrorDialog() = 0;
-
   Delegate* delegate() { return delegate_; }
   void set_delegate(Delegate* delegate) { delegate_ = delegate; }
 
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
index eb4de186..a8930a91 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
@@ -91,12 +91,6 @@
   dialog_->Show();
 }
 
-void LoginDisplayHostMojo::ShowUnrecoverableCrypthomeErrorDialog() {
-  DCHECK(GetOobeUI());
-  GetOobeUI()->signin_screen_handler()->ShowUnrecoverableCrypthomeErrorDialog();
-  dialog_->Show();
-}
-
 void LoginDisplayHostMojo::ShowErrorScreen(LoginDisplay::SigninError error_id) {
   DCHECK(GetOobeUI());
   GetOobeUI()->signin_screen_handler()->ShowErrorScreen(error_id);
@@ -454,6 +448,12 @@
   }
 }
 
+void LoginDisplayHostMojo::OnPasswordChangeDetected() {}
+
+void LoginDisplayHostMojo::OnOldEncryptionDetected(
+    const UserContext& user_context,
+    bool has_incomplete_migration) {}
+
 void LoginDisplayHostMojo::LoadOobeDialog() {
   if (dialog_)
     return;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
index 6219862..a8626ea 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
@@ -50,9 +50,6 @@
   // signin but whitelist check fails.
   void ShowWhitelistCheckFailedError();
 
-  // Show unrecoverable cryptohome error dialog.
-  void ShowUnrecoverableCrypthomeErrorDialog();
-
   // Displays detailed error screen for error with ID |error_id|.
   void ShowErrorScreen(LoginDisplay::SigninError error_id);
 
@@ -119,6 +116,9 @@
   // AuthStatusConsumer:
   void OnAuthFailure(const AuthFailure& error) override;
   void OnAuthSuccess(const UserContext& user_context) override;
+  void OnPasswordChangeDetected() override;
+  void OnOldEncryptionDetected(const UserContext& user_context,
+                               bool has_incomplete_migration) override;
 
  private:
   void LoadOobeDialog();
diff --git a/chrome/browser/chromeos/login/ui/login_display_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_mojo.cc
index 501fe8d..887f23f 100644
--- a/chrome/browser/chromeos/login/ui/login_display_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_mojo.cc
@@ -169,10 +169,6 @@
   host_->ShowWhitelistCheckFailedError();
 }
 
-void LoginDisplayMojo::ShowUnrecoverableCrypthomeErrorDialog() {
-  host_->ShowUnrecoverableCrypthomeErrorDialog();
-}
-
 void LoginDisplayMojo::Login(const UserContext& user_context,
                              const SigninSpecifics& specifics) {
   if (delegate_)
diff --git a/chrome/browser/chromeos/login/ui/login_display_mojo.h b/chrome/browser/chromeos/login/ui/login_display_mojo.h
index 835277b..1b2969d7 100644
--- a/chrome/browser/chromeos/login/ui/login_display_mojo.h
+++ b/chrome/browser/chromeos/login/ui/login_display_mojo.h
@@ -45,7 +45,6 @@
                                  const std::string& email) override;
   void ShowSigninUI(const std::string& email) override;
   void ShowWhitelistCheckFailedError() override;
-  void ShowUnrecoverableCrypthomeErrorDialog() override;
 
   // SigninScreenHandlerDelegate:
   void Login(const UserContext& user_context,
diff --git a/chrome/browser/chromeos/login/ui/login_display_webui.cc b/chrome/browser/chromeos/login/ui/login_display_webui.cc
index 1126bb3..bba65bf9 100644
--- a/chrome/browser/chromeos/login/ui/login_display_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_webui.cc
@@ -178,11 +178,6 @@
     webui_handler_->ShowWhitelistCheckFailedError();
 }
 
-void LoginDisplayWebUI::ShowUnrecoverableCrypthomeErrorDialog() {
-  if (webui_handler_)
-    webui_handler_->ShowUnrecoverableCrypthomeErrorDialog();
-}
-
 // LoginDisplayWebUI, NativeWindowDelegate implementation: ---------------------
 gfx::NativeWindow LoginDisplayWebUI::GetNativeWindow() const {
   return parent_window();
diff --git a/chrome/browser/chromeos/login/ui/login_display_webui.h b/chrome/browser/chromeos/login/ui/login_display_webui.h
index 89ad383..8198e19 100644
--- a/chrome/browser/chromeos/login/ui/login_display_webui.h
+++ b/chrome/browser/chromeos/login/ui/login_display_webui.h
@@ -47,7 +47,6 @@
                                  const std::string& email) override;
   void ShowSigninUI(const std::string& email) override;
   void ShowWhitelistCheckFailedError() override;
-  void ShowUnrecoverableCrypthomeErrorDialog() override;
 
   // NativeWindowDelegate implementation:
   gfx::NativeWindow GetNativeWindow() const override;
diff --git a/chrome/browser/chromeos/login/ui/mock_login_display.h b/chrome/browser/chromeos/login/ui/mock_login_display.h
index b79e86e..6943da1 100644
--- a/chrome/browser/chromeos/login/ui/mock_login_display.h
+++ b/chrome/browser/chromeos/login/ui/mock_login_display.h
@@ -26,7 +26,6 @@
   MOCK_METHOD2(ShowPasswordChangedDialog, void(bool, const std::string&));
   MOCK_METHOD1(ShowSigninUI, void(const std::string&));
   MOCK_METHOD0(ShowWhitelistCheckFailedError, void(void));
-  MOCK_METHOD0(ShowUnrecoverableCrypthomeErrorDialog, void());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockLoginDisplay);
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index 568bbe44..d8d37f51 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -980,12 +980,7 @@
 }
 
 void WizardController::OnSyncConsentFinished() {
-  if (chromeos::quick_unlock::IsFingerprintEnabled(
-          ProfileManager::GetActiveUserProfile())) {
-    ShowFingerprintSetupScreen();
-  } else {
-    ShowDiscoverScreen();
-  }
+  ShowFingerprintSetupScreen();
 }
 
 void WizardController::OnFingerprintSetupScreenExit() {
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index 826816c..4de98d8 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -2879,8 +2879,6 @@
 
 // TODO(alemate): Add tests for Discover UI.
 
-// TODO(xiaoyinh): Add tests for Fingerprint Setup UI.
-
 // TODO(alemate): Add tests for Marketing Opt-In.
 
 // TODO(khorimoto): Add tests for MultiDevice Setup UI.
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_initializer.cc b/chrome/browser/chromeos/policy/device_cloud_policy_initializer.cc
index e6ce8211..4f295c1d 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_initializer.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_initializer.cc
@@ -17,11 +17,11 @@
 #include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/enrollment_config.h"
 #include "chrome/browser/chromeos/policy/enrollment_handler_chromeos.h"
 #include "chrome/browser/chromeos/policy/enrollment_status_chromeos.h"
 #include "chrome/browser/chromeos/policy/server_backed_device_state.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/common/chrome_content_client.h"
 #include "chrome/common/pref_names.h"
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index 13b4644..d77b6698 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -26,10 +26,10 @@
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/heartbeat_scheduler.h"
 #include "chrome/browser/chromeos/policy/remote_commands/device_commands_factory_chromeos.h"
 #include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/status_uploader.h"
 #include "chrome/browser/chromeos/policy/system_log_uploader.h"
 #include "chrome/common/pref_names.h"
diff --git a/chrome/browser/chromeos/policy/device_status_collector.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
similarity index 95%
rename from chrome/browser/chromeos/policy/device_status_collector.cc
rename to chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
index f025bd41..b600316 100644
--- a/chrome/browser/chromeos/policy/device_status_collector.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.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/policy/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -315,31 +315,6 @@
           std::move(callback)));
 }
 
-// Returns the DeviceLocalAccount associated with the current kiosk session.
-// Returns null if there is no active kiosk session, or if that kiosk
-// session has been removed from policy since the session started, in which
-// case we won't report its status).
-std::unique_ptr<policy::DeviceLocalAccount> GetCurrentKioskDeviceLocalAccount(
-    chromeos::CrosSettings* settings) {
-  if (!user_manager::UserManager::Get()->IsLoggedInAsKioskApp() &&
-      !user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp()) {
-    return std::unique_ptr<policy::DeviceLocalAccount>();
-  }
-  const user_manager::User* const user =
-      user_manager::UserManager::Get()->GetActiveUser();
-  const std::vector<policy::DeviceLocalAccount> accounts =
-      policy::GetDeviceLocalAccounts(settings);
-
-  for (const auto& device_local_account : accounts) {
-    if (AccountId::FromUserEmail(device_local_account.user_id) ==
-        user->GetAccountId()) {
-      return std::make_unique<policy::DeviceLocalAccount>(device_local_account);
-    }
-  }
-  LOG(WARNING) << "Kiosk app not found in list of device-local accounts";
-  return std::unique_ptr<policy::DeviceLocalAccount>();
-}
-
 base::Version GetPlatformVersion() {
   return base::Version(base::SysInfo::OperatingSystemVersion());
 }
@@ -396,20 +371,20 @@
  public:
   explicit GetStatusState(
       const scoped_refptr<base::SequencedTaskRunner> task_runner,
-      const policy::DeviceStatusCollector::StatusCallback& response)
+      const policy::StatusCollectorCallback& response)
       : task_runner_(task_runner), response_(response) {}
 
   inline em::DeviceStatusReportRequest* device_status() {
-    return device_status_.get();
+    return response_params_.device_status.get();
   }
 
   inline em::SessionStatusReportRequest* session_status() {
-    return session_status_.get();
+    return response_params_.session_status.get();
   }
 
-  inline void ResetDeviceStatus() { device_status_.reset(); }
+  inline void ResetDeviceStatus() { response_params_.device_status.reset(); }
 
-  inline void ResetSessionStatus() { session_status_.reset(); }
+  inline void ResetSessionStatus() { response_params_.session_status.reset(); }
 
   // Queues an async callback to query disk volume information.
   void SampleVolumeInfo(const policy::DeviceStatusCollector::VolumeInfoFetcher&
@@ -473,27 +448,26 @@
   // not called.
   ~GetStatusState() {
     task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(response_, base::Passed(&device_status_),
-                                  base::Passed(&session_status_)));
+        FROM_HERE, base::BindOnce(response_, base::Passed(&response_params_)));
   }
 
   void OnVolumeInfoReceived(const std::vector<em::VolumeInfo>& volume_info) {
-    device_status_->clear_volume_infos();
+    response_params_.device_status->clear_volume_infos();
     for (const em::VolumeInfo& info : volume_info)
-      *device_status_->add_volume_infos() = info;
+      *response_params_.device_status->add_volume_infos() = info;
   }
 
   void OnCPUTempInfoReceived(
       const std::vector<em::CPUTempInfo>& cpu_temp_info) {
     // Only one of OnProbeDataReceived and OnCPUTempInfoReceived should be
     // called.
-    DCHECK(device_status_->cpu_temp_infos_size() == 0);
+    DCHECK(response_params_.device_status->cpu_temp_infos_size() == 0);
 
     DLOG_IF(WARNING, cpu_temp_info.empty())
         << "Unable to read CPU temp information.";
     base::Time timestamp = base::Time::Now();
     for (const em::CPUTempInfo& info : cpu_temp_info) {
-      auto* new_info = device_status_->add_cpu_temp_infos();
+      auto* new_info = response_params_.device_status->add_cpu_temp_infos();
       *new_info = info;
       new_info->set_timestamp(timestamp.ToJavaTime());
     }
@@ -502,7 +476,7 @@
   void OnAndroidInfoReceived(const std::string& status,
                              const std::string& droid_guard_info) {
     em::AndroidStatus* const android_status =
-        session_status_->mutable_android_status();
+        response_params_.session_status->mutable_android_status();
     android_status->set_status_payload(status);
     android_status->set_droid_guard_info(droid_guard_info);
   }
@@ -511,7 +485,7 @@
     // Make sure we edit the state on the right thread.
     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
     em::TpmStatusInfo* const tpm_status_proto =
-        device_status_->mutable_tpm_status_info();
+        response_params_.device_status->mutable_tpm_status_info();
 
     tpm_status_proto->set_enabled(tpm_status_struct.enabled);
     tpm_status_proto->set_owned(tpm_status_struct.owned);
@@ -540,13 +514,13 @@
 
     // Only one of OnProbeDataReceived and OnCPUTempInfoReceived should be
     // called.
-    DCHECK(device_status_->cpu_temp_infos_size() == 0);
+    DCHECK(response_params_.device_status->cpu_temp_infos_size() == 0);
 
     // Store CPU measurement samples.
     for (const std::unique_ptr<SampledData>& sample_data : samples) {
       for (auto it = sample_data->cpu_samples.begin();
            it != sample_data->cpu_samples.end(); it++) {
-        auto* new_info = device_status_->add_cpu_temp_infos();
+        auto* new_info = response_params_.device_status->add_cpu_temp_infos();
         *new_info = it->second;
       }
     }
@@ -559,7 +533,7 @@
     }
     if (probe_result.value().battery_size() > 0) {
       em::PowerStatus* const power_status =
-          device_status_->mutable_power_status();
+          response_params_.device_status->mutable_power_status();
       for (const auto& battery : probe_result.value().battery()) {
         em::BatteryInfo* const battery_info = power_status->add_batteries();
         battery_info->set_serial(battery.values().serial_number());
@@ -583,7 +557,7 @@
     }
     if (probe_result.value().storage_size() > 0) {
       em::StorageStatus* const storage_status =
-          device_status_->mutable_storage_status();
+          response_params_.device_status->mutable_storage_status();
       for (const auto& storage : probe_result.value().storage()) {
         em::DiskInfo* const disk_info = storage_status->add_disks();
         disk_info->set_serial(base::NumberToString(storage.values().serial()));
@@ -597,11 +571,8 @@
   }
 
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  policy::DeviceStatusCollector::StatusCallback response_;
-  std::unique_ptr<em::DeviceStatusReportRequest> device_status_ =
-      std::make_unique<em::DeviceStatusReportRequest>();
-  std::unique_ptr<em::SessionStatusReportRequest> session_status_ =
-      std::make_unique<em::SessionStatusReportRequest>();
+  policy::StatusCollectorCallback response_;
+  StatusCollectorParams response_params_;
 };
 
 // Handles storing activity time periods needed for reporting. Provides
@@ -1353,28 +1324,6 @@
   last_active_check_ = now;
 }
 
-std::unique_ptr<DeviceLocalAccount>
-DeviceStatusCollector::GetAutoLaunchedKioskSessionInfo() {
-  std::unique_ptr<DeviceLocalAccount> account =
-      GetCurrentKioskDeviceLocalAccount(cros_settings_);
-  if (account) {
-    chromeos::KioskAppManager::App current_app;
-    bool regular_app_auto_launched_with_zero_delay =
-        chromeos::KioskAppManager::Get()->GetApp(account->kiosk_app_id,
-                                                 &current_app) &&
-        current_app.was_auto_launched_with_zero_delay;
-    bool arc_app_auto_launched_with_zero_delay =
-        chromeos::ArcKioskAppManager::Get()
-            ->current_app_was_auto_launched_with_zero_delay();
-    if (regular_app_auto_launched_with_zero_delay ||
-        arc_app_auto_launched_with_zero_delay) {
-      return account;
-    }
-  }
-  // No auto-launched kiosk session active.
-  return std::unique_ptr<DeviceLocalAccount>();
-}
-
 void DeviceStatusCollector::SampleResourceUsage() {
   // Results must be written in the creation thread since that's where they
   // are read from in the Get*StatusAsync methods.
@@ -1787,8 +1736,9 @@
 
   // Don't write any network state if we aren't in a kiosk or public session.
   if (!GetAutoLaunchedKioskSessionInfo() &&
-      !user_manager::UserManager::Get()->IsLoggedInAsPublicAccount())
+      !user_manager::UserManager::Get()->IsLoggedInAsPublicAccount()) {
     return anything_reported;
+  }
 
   // Walk the various networks and store their state in the status report.
   chromeos::NetworkStateHandler::NetworkStateList state_list;
@@ -1996,8 +1946,8 @@
   return true;
 }
 
-void DeviceStatusCollector::GetDeviceAndSessionStatusAsync(
-    const StatusCallback& response) {
+void DeviceStatusCollector::GetStatusAsync(
+    const StatusCollectorCallback& response) {
   // Must be on creation thread since some stats are written to in that thread
   // and accessing them from another thread would lead to race conditions.
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -2200,12 +2150,27 @@
   return extension->VersionString();
 }
 
+// TODO(crbug.com/827386): move public API methods above private ones after
+// common methods are extracted.
 void DeviceStatusCollector::OnSubmittedSuccessfully() {
   activity_storage_->TrimActivityPeriods(last_reported_day_,
                                          duration_for_last_reported_day_,
                                          std::numeric_limits<int64_t>::max());
 }
 
+bool DeviceStatusCollector::ShouldReportActivityTimes() const {
+  return report_activity_times_;
+}
+bool DeviceStatusCollector::ShouldReportNetworkInterfaces() const {
+  return report_network_interfaces_;
+}
+bool DeviceStatusCollector::ShouldReportUsers() const {
+  return report_users_;
+}
+bool DeviceStatusCollector::ShouldReportHardwareStatus() const {
+  return report_hardware_status_;
+}
+
 void DeviceStatusCollector::OnOSVersion(const std::string& version) {
   os_version_ = version;
 }
diff --git a/chrome/browser/chromeos/policy/device_status_collector.h b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
similarity index 91%
rename from chrome/browser/chromeos/policy/device_status_collector.h
rename to chrome/browser/chromeos/policy/status_collector/device_status_collector.h
index ce15dd7e..be309f03 100644
--- a/chrome/browser/chromeos/policy/device_status_collector.h
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DEVICE_STATUS_COLLECTOR_H_
-#define CHROME_BROWSER_CHROMEOS_POLICY_DEVICE_STATUS_COLLECTOR_H_
+#ifndef CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_DEVICE_STATUS_COLLECTOR_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_DEVICE_STATUS_COLLECTOR_H_
 
 #include <stdint.h>
 
@@ -25,6 +25,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/chromeos/child_accounts/usage_time_state_notifier.h"
+#include "chrome/browser/chromeos/policy/status_collector/status_collector.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chromeos/dbus/cryptohome/cryptohome_client.h"
 #include "chromeos/dbus/power/power_manager_client.h"
@@ -41,7 +42,7 @@
 namespace system {
 class StatisticsProvider;
 }
-}
+}  // namespace chromeos
 
 namespace cryptohome {
 struct TpmStatusInfo;
@@ -112,13 +113,14 @@
 };
 
 // Collects and summarizes the status of an enterprised-managed ChromeOS device.
-class DeviceStatusCollector : public session_manager::SessionManagerObserver,
+class DeviceStatusCollector : public StatusCollector,
+                              public session_manager::SessionManagerObserver,
                               public chromeos::UsageTimeStateNotifier::Observer,
                               public chromeos::PowerManagerClient::Observer {
  public:
-  using VolumeInfoFetcher = base::Callback<
-    std::vector<enterprise_management::VolumeInfo>(
-        const std::vector<std::string>& mount_points)>;
+  using VolumeInfoFetcher =
+      base::Callback<std::vector<enterprise_management::VolumeInfo>(
+          const std::vector<std::string>& mount_points)>;
 
   // Reads the first CPU line from /proc/stat. Returns an empty string if
   // the cpu data could not be read. Broken out into a callback to enable
@@ -157,12 +159,6 @@
   // Gets the ProbeResult/sampled data and passes it to ProbeDataReceiver.
   using ProbeDataFetcher = base::RepeatingCallback<void(ProbeDataReceiver)>;
 
-  // Called in the UI thread after the device and session status have been
-  // collected asynchronously in GetDeviceAndSessionStatusAsync. Null pointers
-  // indicate errors or that device or session status reporting is disabled.
-  using StatusCallback = base::Callback<void(
-      std::unique_ptr<enterprise_management::DeviceStatusReportRequest>,
-      std::unique_ptr<enterprise_management::SessionStatusReportRequest>)>;
   // Constructor. Callers can inject their own *Fetcher callbacks, e.g. for unit
   // testing. A null callback can be passed for any *Fetcher parameter, to use
   // the default implementation. These callbacks are always executed on Blocking
@@ -182,29 +178,17 @@
                         bool is_enterprise_reporting);
   ~DeviceStatusCollector() override;
 
-  // Gathers device and session status information and calls the passed response
-  // callback. Null pointers passed into the response indicate errors or that
-  // device or session status reporting is disabled.
-  virtual void GetDeviceAndSessionStatusAsync(const StatusCallback& response);
-
-  // Called after the status information has successfully been submitted to
-  // the server.
-  virtual void OnSubmittedSuccessfully();
+  // StatusCollector:
+  void GetStatusAsync(const StatusCollectorCallback& response) override;
+  void OnSubmittedSuccessfully() override;
+  bool ShouldReportActivityTimes() const override;
+  bool ShouldReportNetworkInterfaces() const override;
+  bool ShouldReportUsers() const override;
+  bool ShouldReportHardwareStatus() const override;
 
   static void RegisterPrefs(PrefRegistrySimple* registry);
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  // Returns the DeviceLocalAccount associated with the currently active
-  // kiosk session, if the session was auto-launched with zero delay
-  // (this enables functionality such as network reporting).
-  // Virtual to allow mocking.
-  virtual std::unique_ptr<DeviceLocalAccount> GetAutoLaunchedKioskSessionInfo();
-
-  bool report_activity_times() const { return report_activity_times_; }
-  bool report_network_interfaces() const { return report_network_interfaces_; }
-  bool report_users() const { return report_users_; }
-  bool report_hardware_status() const { return report_hardware_status_; }
-
   // How often, in seconds, to poll to see if the user is idle.
   static const unsigned int kIdlePollIntervalSeconds = 30;
 
@@ -520,4 +504,4 @@
 
 }  // namespace policy
 
-#endif  // CHROME_BROWSER_CHROMEOS_POLICY_DEVICE_STATUS_COLLECTOR_H_
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_DEVICE_STATUS_COLLECTOR_H_
diff --git a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
similarity index 99%
rename from chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
rename to chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
index f2854eb..fb2fa40 100644
--- a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.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/policy/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -178,8 +178,8 @@
     kiosk_account_ = std::move(account);
   }
 
-  std::unique_ptr<policy::DeviceLocalAccount> GetAutoLaunchedKioskSessionInfo()
-      override {
+  std::unique_ptr<policy::DeviceLocalAccount>
+  GetAutoLaunchedKioskSessionInfo() override {
     if (kiosk_account_)
       return std::make_unique<policy::DeviceLocalAccount>(*kiosk_account_);
     return std::unique_ptr<policy::DeviceLocalAccount>();
@@ -513,20 +513,18 @@
     session_status_.Clear();
     got_session_status_ = false;
     run_loop_.reset(new base::RunLoop());
-    status_collector_->GetDeviceAndSessionStatusAsync(base::BindRepeating(
+    status_collector_->GetStatusAsync(base::BindRepeating(
         &DeviceStatusCollectorTest::OnStatusReceived, base::Unretained(this)));
     run_loop_->Run();
     run_loop_.reset();
   }
 
-  void OnStatusReceived(
-      std::unique_ptr<em::DeviceStatusReportRequest> device_status,
-      std::unique_ptr<em::SessionStatusReportRequest> session_status) {
-    if (device_status)
-      device_status_ = *device_status;
-    got_session_status_ = session_status != nullptr;
+  void OnStatusReceived(StatusCollectorParams callback_params) {
+    if (callback_params.device_status)
+      device_status_ = *callback_params.device_status;
+    got_session_status_ = callback_params.session_status != nullptr;
     if (got_session_status_)
-      session_status_ = *session_status;
+      session_status_ = *callback_params.session_status;
     EXPECT_TRUE(run_loop_);
     run_loop_->Quit();
   }
diff --git a/chrome/browser/chromeos/policy/status_collector/status_collector.cc b/chrome/browser/chromeos/policy/status_collector/status_collector.cc
new file mode 100644
index 0000000..a0aa92a0
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/status_collector.cc
@@ -0,0 +1,80 @@
+// 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/policy/status_collector/status_collector.h"
+
+#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/chromeos/policy/device_local_account.h"
+#include "components/user_manager/user_manager.h"
+
+namespace policy {
+namespace {
+
+namespace ent_mgmt = ::enterprise_management;
+
+// Returns the DeviceLocalAccount associated with the current kiosk session.
+// Returns nullptr if there is no active kiosk session, or if that kiosk
+// session has been removed from policy since the session started, in which
+// case we won't report its status).
+std::unique_ptr<DeviceLocalAccount> GetCurrentKioskDeviceLocalAccount(
+    chromeos::CrosSettings* settings) {
+  if (!user_manager::UserManager::Get()->IsLoggedInAsKioskApp() &&
+      !user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp()) {
+    return nullptr;
+  }
+  const user_manager::User* const user =
+      user_manager::UserManager::Get()->GetActiveUser();
+  const std::vector<DeviceLocalAccount> accounts =
+      GetDeviceLocalAccounts(settings);
+
+  for (const auto& device_local_account : accounts) {
+    if (AccountId::FromUserEmail(device_local_account.user_id) ==
+        user->GetAccountId()) {
+      return std::make_unique<DeviceLocalAccount>(device_local_account);
+    }
+  }
+  LOG(WARNING) << "Kiosk app not found in list of device-local accounts";
+  return nullptr;
+}
+
+}  // namespace
+
+StatusCollectorParams::StatusCollectorParams() {
+  device_status = std::make_unique<ent_mgmt::DeviceStatusReportRequest>();
+  session_status = std::make_unique<ent_mgmt::SessionStatusReportRequest>();
+  child_status = std::make_unique<ent_mgmt::ChildStatusReportRequest>();
+}
+StatusCollectorParams::~StatusCollectorParams() = default;
+
+// Move only.
+StatusCollectorParams::StatusCollectorParams(StatusCollectorParams&&) = default;
+StatusCollectorParams& StatusCollectorParams::operator=(
+    StatusCollectorParams&&) = default;
+
+std::unique_ptr<DeviceLocalAccount>
+StatusCollector::GetAutoLaunchedKioskSessionInfo() {
+  std::unique_ptr<DeviceLocalAccount> account =
+      GetCurrentKioskDeviceLocalAccount(chromeos::CrosSettings::Get());
+  if (!account) {
+    // No auto-launched kiosk session active.
+    return nullptr;
+  }
+
+  chromeos::KioskAppManager::App current_app;
+  bool regular_app_auto_launched_with_zero_delay =
+      chromeos::KioskAppManager::Get()->GetApp(account->kiosk_app_id,
+                                               &current_app) &&
+      current_app.was_auto_launched_with_zero_delay;
+  bool arc_app_auto_launched_with_zero_delay =
+      chromeos::ArcKioskAppManager::Get()
+          ->current_app_was_auto_launched_with_zero_delay();
+
+  return regular_app_auto_launched_with_zero_delay ||
+                 arc_app_auto_launched_with_zero_delay
+             ? std::move(account)
+             : nullptr;
+}
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/status_collector/status_collector.h b/chrome/browser/chromeos/policy/status_collector/status_collector.h
new file mode 100644
index 0000000..8110556f
--- /dev/null
+++ b/chrome/browser/chromeos/policy/status_collector/status_collector.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 CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace policy {
+
+struct DeviceLocalAccount;
+
+// Groups parameters that are necessary either directly in the
+// |StatusCollectorCallback| or in async methods that work as callbacks and
+// expect these exact same parameters. Absence of values indicates errors or
+// that status reporting is disabled.
+//
+// Notice that the fields are used in different contexts, depending on the type
+// of user:
+// - Enterprise users: |device_status| and |session_status|.
+// - Child users:
+//    - During the migration away from DeviceStatusCollector:
+//      |device_status|, |session_status|, |child_status|.
+//    - After migration: only |child_status|.
+struct StatusCollectorParams {
+  StatusCollectorParams();
+  ~StatusCollectorParams();
+
+  // Move only.
+  StatusCollectorParams(StatusCollectorParams&&);
+  StatusCollectorParams& operator=(StatusCollectorParams&&);
+
+  std::unique_ptr<enterprise_management::DeviceStatusReportRequest>
+      device_status;
+  std::unique_ptr<enterprise_management::SessionStatusReportRequest>
+      session_status;
+  std::unique_ptr<enterprise_management::ChildStatusReportRequest> child_status;
+};
+
+// Called in the UI thread after the statuses have been collected
+// asynchronously.
+using StatusCollectorCallback =
+    base::RepeatingCallback<void(StatusCollectorParams)>;
+
+// Defines the API for a status collector.
+class StatusCollector {
+ public:
+  StatusCollector() = default;
+  virtual ~StatusCollector() = default;
+
+  // Gathers status information and calls the passed response callback.
+  virtual void GetStatusAsync(const StatusCollectorCallback& callback) = 0;
+
+  // Called after the status information has successfully been submitted to
+  // the server.
+  virtual void OnSubmittedSuccessfully() = 0;
+
+  // Methods used to decide whether a specific categories of data should be
+  // included in the reports or not. See:
+  // https://cs.chromium.org/search/?q=AddDeviceReportingInfo
+  virtual bool ShouldReportActivityTimes() const = 0;
+  virtual bool ShouldReportNetworkInterfaces() const = 0;
+  virtual bool ShouldReportUsers() const = 0;
+  virtual bool ShouldReportHardwareStatus() const = 0;
+
+  // Returns the DeviceLocalAccount associated with the currently active kiosk
+  // session, if the session was auto-launched with zero delay (this enables
+  // functionality such as network reporting). If it isn't a kiosk session,
+  // nullptr is returned.
+  virtual std::unique_ptr<DeviceLocalAccount> GetAutoLaunchedKioskSessionInfo();
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_STATUS_COLLECTOR_STATUS_COLLECTOR_H_
diff --git a/chrome/browser/chromeos/policy/status_uploader.cc b/chrome/browser/chromeos/policy/status_uploader.cc
index 3770773f..363b609 100644
--- a/chrome/browser/chromeos/policy/status_uploader.cc
+++ b/chrome/browser/chromeos/policy/status_uploader.cc
@@ -15,7 +15,7 @@
 #include "base/syslog_logging.h"
 #include "base/system/sys_info.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/status_collector.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "chromeos/settings/cros_settings_provider.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
@@ -41,7 +41,7 @@
 
 StatusUploader::StatusUploader(
     CloudPolicyClient* client,
-    std::unique_ptr<DeviceStatusCollector> collector,
+    std::unique_ptr<StatusCollector> collector,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     base::TimeDelta default_upload_frequency)
     : client_(client),
@@ -188,16 +188,15 @@
 void StatusUploader::UploadStatus() {
   status_upload_in_progress_ = true;
   // Gather status in the background.
-  collector_->GetDeviceAndSessionStatusAsync(base::Bind(
-      &StatusUploader::OnStatusReceived, weak_factory_.GetWeakPtr()));
+  collector_->GetStatusAsync(base::Bind(&StatusUploader::OnStatusReceived,
+                                        weak_factory_.GetWeakPtr()));
 }
 
-void StatusUploader::OnStatusReceived(
-    std::unique_ptr<em::DeviceStatusReportRequest> device_status,
-    std::unique_ptr<em::SessionStatusReportRequest> session_status) {
-  bool have_device_status = device_status != nullptr;
-  bool have_session_status = session_status != nullptr;
-  if (!have_device_status && !have_session_status) {
+void StatusUploader::OnStatusReceived(StatusCollectorParams callback_params) {
+  bool has_device_status = callback_params.device_status != nullptr;
+  bool has_session_status = callback_params.session_status != nullptr;
+  bool has_child_status = callback_params.child_status != nullptr;
+  if (!has_device_status && !has_session_status && !has_child_status) {
     SYSLOG(INFO) << "Skipping status upload because no data to upload";
     // Don't have any status to upload - just set our timer for next time.
     last_upload_ = base::Time::NowFromSystemTime();
@@ -215,9 +214,11 @@
     return;
   }
 
-  SYSLOG(INFO) << "Starting status upload: have_device_status = "
-               << have_device_status;
-  client_->UploadDeviceStatus(device_status.get(), session_status.get(),
+  SYSLOG(INFO) << "Starting status upload: has_device_status = "
+               << has_device_status;
+  client_->UploadDeviceStatus(callback_params.device_status.get(),
+                              callback_params.session_status.get(),
+                              callback_params.child_status.get(),
                               base::Bind(&StatusUploader::OnUploadCompleted,
                                          weak_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/chromeos/policy/status_uploader.h b/chrome/browser/chromeos/policy/status_uploader.h
index 56f6e68e..9463260 100644
--- a/chrome/browser/chromeos/policy/status_uploader.h
+++ b/chrome/browser/chromeos/policy/status_uploader.h
@@ -7,11 +7,13 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "base/cancelable_callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
+#include "chrome/browser/chromeos/policy/device_local_account.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
@@ -24,17 +26,18 @@
 namespace policy {
 
 class CloudPolicyClient;
-class DeviceStatusCollector;
+class StatusCollector;
+struct StatusCollectorParams;
 
 // Class responsible for periodically uploading device status from the
-// passed DeviceStatusCollector.
+// passed StatusCollector.
 class StatusUploader : public MediaCaptureDevicesDispatcher::Observer {
  public:
   // Constructor. |client| must be registered and must stay
   // valid and registered through the lifetime of this StatusUploader
   // object.
   StatusUploader(CloudPolicyClient* client,
-                 std::unique_ptr<DeviceStatusCollector> collector,
+                 std::unique_ptr<StatusCollector> collector,
                  const scoped_refptr<base::SequencedTaskRunner>& task_runner,
                  base::TimeDelta default_upload_frequency);
 
@@ -59,21 +62,15 @@
   // Returns false if there is already an ongoing status report.
   bool ScheduleNextStatusUploadImmediately();
 
-  const DeviceStatusCollector* device_status_collector() const {
-    return collector_.get();
-  }
+  StatusCollector* status_collector() const { return collector_.get(); }
 
  private:
   // Callback invoked periodically to upload the device status from the
-  // DeviceStatusCollector.
+  // StatusCollector.
   void UploadStatus();
 
-  // Called asynchronously by DeviceStatusCollector when status arrives
-  void OnStatusReceived(
-      std::unique_ptr<enterprise_management::DeviceStatusReportRequest>
-          device_status,
-      std::unique_ptr<enterprise_management::SessionStatusReportRequest>
-          session_status);
+  // Called asynchronously by StatusCollector when status arrives.
+  void OnStatusReceived(StatusCollectorParams callback_params);
 
   // Invoked once a status upload has completed.
   void OnUploadCompleted(bool success);
@@ -90,8 +87,8 @@
   // CloudPolicyClient used to issue requests to the server.
   CloudPolicyClient* client_;
 
-  // DeviceStatusCollector that provides status for uploading.
-  std::unique_ptr<DeviceStatusCollector> collector_;
+  // StatusCollector that provides status for uploading.
+  std::unique_ptr<StatusCollector> collector_;
 
   // TaskRunner used for scheduling upload tasks.
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/chrome/browser/chromeos/policy/status_uploader_unittest.cc b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
index 4956928..a1bcf519 100644
--- a/chrome/browser/chromeos/policy/status_uploader_unittest.cc
+++ b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
 #include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -64,8 +64,7 @@
             base::TimeDelta(), /* Day starts at midnight */
             true /* is_enterprise_device */) {}
 
-  MOCK_METHOD1(GetDeviceAndSessionStatusAsync,
-               void(const policy::DeviceStatusCollector::StatusCallback&));
+  MOCK_METHOD1(GetStatusAsync, void(const policy::StatusCollectorCallback&));
 
   MOCK_METHOD0(OnSubmittedSuccessfully, void());
 
@@ -112,12 +111,12 @@
   // Given a pending task to upload status, runs the task and returns the
   // callback waiting to get device status / session status. The status upload
   // task will be blocked until the test code calls that callback.
-  DeviceStatusCollector::StatusCallback CollectStatusCallback() {
+  StatusCollectorCallback CollectStatusCallback() {
     // Running the task should pass a callback into
-    // GetDeviceAndSessionStatusAsync. We'll grab this callback.
+    // GetStatusAsync. We'll grab this callback.
     EXPECT_TRUE(task_runner_->HasPendingTask());
-    DeviceStatusCollector::StatusCallback status_callback;
-    EXPECT_CALL(*collector_ptr_, GetDeviceAndSessionStatusAsync(_))
+    StatusCollectorCallback status_callback;
+    EXPECT_CALL(*collector_ptr_, GetStatusAsync)
         .WillOnce(SaveArg<0>(&status_callback));
     task_runner_->RunPendingTasks();
     testing::Mock::VerifyAndClearExpectations(&device_management_service_);
@@ -129,22 +128,17 @@
   void RunPendingUploadTaskAndCheckNext(const StatusUploader& uploader,
                                         base::TimeDelta expected_delay,
                                         bool upload_success) {
-    DeviceStatusCollector::StatusCallback status_callback =
-        CollectStatusCallback();
+    StatusCollectorCallback status_callback = CollectStatusCallback();
 
     // Running the status collected callback should trigger
     // CloudPolicyClient::UploadDeviceStatus.
     CloudPolicyClient::StatusCallback callback;
-    EXPECT_CALL(client_, UploadDeviceStatus(_, _, _))
-        .WillOnce(SaveArg<2>(&callback));
+    EXPECT_CALL(client_, UploadDeviceStatus).WillOnce(SaveArg<3>(&callback));
 
     // Send some "valid" (read: non-nullptr) device/session data to the
     // callback in order to simulate valid status data.
-    std::unique_ptr<em::DeviceStatusReportRequest> device_status =
-        std::make_unique<em::DeviceStatusReportRequest>();
-    std::unique_ptr<em::SessionStatusReportRequest> session_status =
-        std::make_unique<em::SessionStatusReportRequest>();
-    status_callback.Run(std::move(device_status), std::move(session_status));
+    StatusCollectorParams status_params;
+    status_callback.Run(std::move(status_params));
 
     testing::Mock::VerifyAndClearExpectations(&device_management_service_);
 
@@ -180,6 +174,12 @@
     EXPECT_GE(next_task, uploader.last_upload() + expected_delay);
   }
 
+  std::unique_ptr<StatusUploader> CreateStatusUploader() {
+    return std::make_unique<StatusUploader>(&client_, std::move(collector_),
+                                            task_runner_,
+                                            kDefaultStatusUploadDelay);
+  }
+
   content::TestBrowserThreadBundle thread_bundle_;
   scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
   chromeos::ScopedTestingCrosSettings scoped_testing_cros_settings_;
@@ -196,8 +196,7 @@
 
 TEST_F(StatusUploaderTest, BasicTest) {
   EXPECT_FALSE(task_runner_->HasPendingTask());
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   EXPECT_EQ(1U, task_runner_->NumPendingTasks());
   // On startup, first update should happen in 1 minute.
   EXPECT_EQ(base::TimeDelta::FromMinutes(1),
@@ -210,26 +209,24 @@
   scoped_testing_cros_settings_.device_settings()->SetInteger(
       chromeos::kReportUploadFrequency, new_delay.InMilliseconds());
   EXPECT_FALSE(task_runner_->HasPendingTask());
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   ASSERT_EQ(1U, task_runner_->NumPendingTasks());
   // On startup, first update should happen in 1 minute.
   EXPECT_EQ(base::TimeDelta::FromMinutes(1),
             task_runner_->NextPendingTaskDelay());
 
   // Second update should use the delay specified in settings.
-  RunPendingUploadTaskAndCheckNext(uploader, new_delay,
+  RunPendingUploadTaskAndCheckNext(*uploader, new_delay,
                                    true /* upload_success */);
 }
 
 TEST_F(StatusUploaderTest, ResetTimerAfterStatusCollection) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
-  RunPendingUploadTaskAndCheckNext(uploader, kDefaultStatusUploadDelay,
+  auto uploader = CreateStatusUploader();
+  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                    true /* upload_success */);
 
   // Handle this response also, and ensure new task is queued.
-  RunPendingUploadTaskAndCheckNext(uploader, kDefaultStatusUploadDelay,
+  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                    true /* upload_success */);
 
   // Now that the previous request was satisfied, a task to do the next
@@ -238,15 +235,14 @@
 }
 
 TEST_F(StatusUploaderTest, ResetTimerAfterFailedStatusCollection) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
 
   // Running the queued task should pass a callback into
-  // GetDeviceAndSessionStatusAsync. We'll grab this callback and send nullptrs
+  // GetStatusAsync. We'll grab this callback and send nullptrs
   // to it in order to simulate failure to get status.
   EXPECT_EQ(1U, task_runner_->NumPendingTasks());
-  DeviceStatusCollector::StatusCallback status_callback;
-  EXPECT_CALL(*collector_ptr_, GetDeviceAndSessionStatusAsync(_))
+  StatusCollectorCallback status_callback;
+  EXPECT_CALL(*collector_ptr_, GetStatusAsync)
       .WillOnce(SaveArg<0>(&status_callback));
   task_runner_->RunPendingTasks();
   testing::Mock::VerifyAndClearExpectations(&device_management_service_);
@@ -254,23 +250,23 @@
   // Running the callback should trigger StatusUploader::OnStatusReceived, which
   // in turn should recognize the failure to get status and queue another status
   // upload.
-  std::unique_ptr<em::DeviceStatusReportRequest> invalid_device_status;
-  std::unique_ptr<em::SessionStatusReportRequest> invalid_session_status;
-  status_callback.Run(std::move(invalid_device_status),
-                      std::move(invalid_session_status));
+  StatusCollectorParams status_params;
+  status_params.device_status.reset();
+  status_params.session_status.reset();
+  status_params.child_status.reset();
+  status_callback.Run(std::move(status_params));
   EXPECT_EQ(1U, task_runner_->NumPendingTasks());
 
   // Check the delay of the queued upload
-  CheckPendingTaskDelay(uploader, kDefaultStatusUploadDelay,
+  CheckPendingTaskDelay(*uploader, kDefaultStatusUploadDelay,
                         task_runner_->NextPendingTaskDelay());
 }
 
 TEST_F(StatusUploaderTest, ResetTimerAfterUploadError) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
 
   // Simulate upload error
-  RunPendingUploadTaskAndCheckNext(uploader, kDefaultStatusUploadDelay,
+  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                    false /* upload_success */);
 
   // Now that the previous request was satisfied, a task to do the next
@@ -279,78 +275,69 @@
 }
 
 TEST_F(StatusUploaderTest, ResetTimerAfterUnregisteredClient) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
 
   client_.SetDMToken("");
   EXPECT_FALSE(client_.is_registered());
 
-  DeviceStatusCollector::StatusCallback status_callback =
-      CollectStatusCallback();
+  StatusCollectorCallback status_callback = CollectStatusCallback();
 
   // Make sure no status upload is queued up yet (since an upload is in
   // progress).
   EXPECT_FALSE(task_runner_->HasPendingTask());
 
   // StatusUploader should not try to upload using an unregistered client
-  EXPECT_CALL(client_, UploadDeviceStatus(_, _, _)).Times(0);
-  std::unique_ptr<em::DeviceStatusReportRequest> device_status =
-      std::make_unique<em::DeviceStatusReportRequest>();
-  std::unique_ptr<em::SessionStatusReportRequest> session_status =
-      std::make_unique<em::SessionStatusReportRequest>();
-  status_callback.Run(std::move(device_status), std::move(session_status));
+  EXPECT_CALL(client_, UploadDeviceStatus).Times(0);
+  StatusCollectorParams status_params;
+  status_callback.Run(std::move(status_params));
 
   // A task to try again should be queued.
   ASSERT_EQ(1U, task_runner_->NumPendingTasks());
 
-  CheckPendingTaskDelay(uploader, kDefaultStatusUploadDelay,
+  CheckPendingTaskDelay(*uploader, kDefaultStatusUploadDelay,
                         task_runner_->NextPendingTaskDelay());
 }
 
 TEST_F(StatusUploaderTest, ChangeFrequency) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   // Change the frequency. The new frequency should be reflected in the timing
   // used for the next callback.
   const base::TimeDelta new_delay = kDefaultStatusUploadDelay * 2;
   scoped_testing_cros_settings_.device_settings()->SetInteger(
       chromeos::kReportUploadFrequency, new_delay.InMilliseconds());
-  RunPendingUploadTaskAndCheckNext(uploader, new_delay,
+  RunPendingUploadTaskAndCheckNext(*uploader, new_delay,
                                    true /* upload_success */);
 }
 
 TEST_F(StatusUploaderTest, NoUploadAfterUserInput) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   // Should allow data upload before there is user input.
-  EXPECT_TRUE(uploader.IsSessionDataUploadAllowed());
+  EXPECT_TRUE(uploader->IsSessionDataUploadAllowed());
 
   // Now mock user input, and no session data should be allowed.
   ui::MouseEvent e(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                    ui::EventTimeForNow(), 0, 0);
   const ui::PlatformEvent& native_event = &e;
   ui::UserActivityDetector::Get()->DidProcessEvent(native_event);
-  EXPECT_FALSE(uploader.IsSessionDataUploadAllowed());
+  EXPECT_FALSE(uploader->IsSessionDataUploadAllowed());
 }
 
 TEST_F(StatusUploaderTest, NoUploadAfterVideoCapture) {
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   // Should allow data upload before there is video capture.
-  EXPECT_TRUE(uploader.IsSessionDataUploadAllowed());
+  EXPECT_TRUE(uploader->IsSessionDataUploadAllowed());
 
   // Now mock video capture, and no session data should be allowed.
   MediaCaptureDevicesDispatcher::GetInstance()->OnMediaRequestStateChanged(
       0, 0, 0, GURL("http://www.google.com"), blink::MEDIA_DEVICE_VIDEO_CAPTURE,
       content::MEDIA_REQUEST_STATE_OPENING);
   base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(uploader.IsSessionDataUploadAllowed());
+  EXPECT_FALSE(uploader->IsSessionDataUploadAllowed());
 }
 
 TEST_F(StatusUploaderTest, ScheduleImmediateStatusUpload) {
   EXPECT_FALSE(task_runner_->HasPendingTask());
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   EXPECT_EQ(1U, task_runner_->NumPendingTasks());
 
   // On startup, first update should happen in 1 minute.
@@ -358,16 +345,15 @@
             task_runner_->NextPendingTaskDelay());
 
   // Schedule an immediate status upload.
-  uploader.ScheduleNextStatusUploadImmediately();
+  uploader->ScheduleNextStatusUploadImmediately();
   EXPECT_EQ(2U, task_runner_->NumPendingTasks());
-  CheckPendingTaskDelay(uploader, base::TimeDelta(),
+  CheckPendingTaskDelay(*uploader, base::TimeDelta(),
                         task_runner_->FinalPendingTaskDelay());
 }
 
 TEST_F(StatusUploaderTest, ScheduleImmediateStatusUploadConsecutively) {
   EXPECT_FALSE(task_runner_->HasPendingTask());
-  StatusUploader uploader(&client_, std::move(collector_), task_runner_,
-                          kDefaultStatusUploadDelay);
+  auto uploader = CreateStatusUploader();
   EXPECT_EQ(1U, task_runner_->NumPendingTasks());
 
   // On startup, first update should happen in 1 minute.
@@ -375,15 +361,15 @@
             task_runner_->NextPendingTaskDelay());
 
   // Schedule an immediate status upload and run it.
-  uploader.ScheduleNextStatusUploadImmediately();
-  RunPendingUploadTaskAndCheckNext(uploader, kDefaultStatusUploadDelay,
+  uploader->ScheduleNextStatusUploadImmediately();
+  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                    true /* upload_success */);
 
   // Schedule the next one and check that it was scheduled after
   // kMinImmediateUploadInterval of the last upload.
-  uploader.ScheduleNextStatusUploadImmediately();
+  uploader->ScheduleNextStatusUploadImmediately();
   EXPECT_EQ(2U, task_runner_->NumPendingTasks());
-  CheckPendingTaskDelay(uploader, kMinImmediateUploadInterval,
+  CheckPendingTaskDelay(*uploader, kMinImmediateUploadInterval,
                         task_runner_->FinalPendingTaskDelay());
 }
 
diff --git a/chrome/browser/chromeos/startup_settings_cache.cc b/chrome/browser/chromeos/startup_settings_cache.cc
new file mode 100644
index 0000000..bd29d71
--- /dev/null
+++ b/chrome/browser/chromeos/startup_settings_cache.cc
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/startup_settings_cache.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+
+namespace chromeos {
+namespace startup_settings_cache {
+namespace {
+
+// Name of the cache file on disk.
+const char kCacheFilename[] = "startup_settings_cache.json";
+
+// JSON dictionary key for application locale (e.g. "ja" or "en_GB").
+const char kAppLocaleKey[] = "app_locale";
+
+bool GetCacheFilePath(base::FilePath* path) {
+  base::FilePath user_data_dir;
+  if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
+    return false;
+
+  *path = user_data_dir.Append(kCacheFilename);
+  return true;
+}
+
+}  // namespace
+
+std::string ReadAppLocale() {
+  base::FilePath cache_file;
+  if (!GetCacheFilePath(&cache_file))
+    return std::string();
+
+  std::string input;
+  if (!base::ReadFileToString(cache_file, &input))
+    return std::string();
+
+  base::Optional<base::Value> settings = base::JSONReader::Read(input);
+  if (!settings.has_value())
+    return std::string();
+
+  base::Value* app_locale_setting = settings->FindKey(kAppLocaleKey);
+  if (!app_locale_setting)
+    return std::string();
+
+  std::string app_locale;
+  app_locale_setting->GetAsString(&app_locale);
+  // The locale is already an "actual locale", so this does not need to call
+  // language::ConvertToActualUILocale().
+  return app_locale;
+}
+
+void WriteAppLocale(std::string app_locale) {
+  base::FilePath cache_file;
+  if (!GetCacheFilePath(&cache_file))
+    return;
+
+  base::Value settings(base::Value::Type::DICTIONARY);
+  settings.SetKey(kAppLocaleKey, base::Value(app_locale));
+
+  std::string output;
+  if (!base::JSONWriter::Write(settings, &output))
+    return;
+
+  // Ignore errors because we're shutting down and we can't recover.
+  base::WriteFile(cache_file, output.data(), static_cast<int>(output.size()));
+}
+
+}  // namespace startup_settings_cache
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/startup_settings_cache.h b/chrome/browser/chromeos/startup_settings_cache.h
new file mode 100644
index 0000000..5954337
--- /dev/null
+++ b/chrome/browser/chromeos/startup_settings_cache.h
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_STARTUP_SETTINGS_CACHE_H_
+#define CHROME_BROWSER_CHROMEOS_STARTUP_SETTINGS_CACHE_H_
+
+#include <string>
+
+namespace chromeos {
+namespace startup_settings_cache {
+
+// On Chrome OS, the application locale is stored in local state prefs. The
+// zygote needs the locale so it can load the correct resource bundle and
+// provide localized strings to renderers. However, the zygote forks and engages
+// the sandbox before the browser loads local state.
+//
+// Instead, cache the locale in a separate JSON file and read it on zygote
+// startup. The additional disk read on startup is unfortunately, but it's only
+// ~20 bytes and this approach performs better than other approaches (passing a
+// resource bundle file descriptor to zygote on renderer fork, or pre-load the
+// local state file on startup). On coral (dual core Celeron N3350 1.1 GHz) the
+// file read takes < 2.5 ms and the write takes < 1 ms. https://crbug.com/510455
+std::string ReadAppLocale();
+
+// Writes the locale string to a JSON file on disk. See above.
+void WriteAppLocale(std::string app_locale);
+
+}  // namespace startup_settings_cache
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_STARTUP_SETTINGS_CACHE_H_
diff --git a/chrome/browser/chromeos/startup_settings_cache_unittest.cc b/chrome/browser/chromeos/startup_settings_cache_unittest.cc
new file mode 100644
index 0000000..716491e7
--- /dev/null
+++ b/chrome/browser/chromeos/startup_settings_cache_unittest.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 "chrome/browser/chromeos/startup_settings_cache.h"
+
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/test/scoped_path_override.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+class StartupSettingsCacheTest : public testing::Test {
+ protected:
+  StartupSettingsCacheTest() : user_data_dir_override_(chrome::DIR_USER_DATA) {}
+  ~StartupSettingsCacheTest() override {}
+
+ private:
+  // Map DIR_USER_DATA to a temp dir.
+  base::ScopedPathOverride user_data_dir_override_;
+
+  DISALLOW_COPY_AND_ASSIGN(StartupSettingsCacheTest);
+};
+
+TEST_F(StartupSettingsCacheTest, RoundTrip) {
+  startup_settings_cache::WriteAppLocale("foo");
+  EXPECT_EQ("foo", startup_settings_cache::ReadAppLocale());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.cc b/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.cc
index 32204cdbd..824a1e6 100644
--- a/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.cc
+++ b/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.cc
@@ -21,9 +21,12 @@
 // The length of the crash ID string.
 constexpr size_t kCrashIdStringSize = 16;
 
-// We are only interested in crashes that took place within the last hour.
+// For recent crashes, which is for all reports, look back one hour.
 constexpr base::TimeDelta kOneHourTimeDelta = base::TimeDelta::FromHours(1);
 
+// For all crashes, which is for only @google.com reports, look back 120 days.
+constexpr base::TimeDelta k120DaysTimeDelta = base::TimeDelta::FromDays(120);
+
 }  // namespace
 
 CrashIdsSource::CrashIdsSource()
@@ -50,13 +53,19 @@
 void CrashIdsSource::OnUploadListAvailable() {
   pending_crash_list_loading_ = false;
 
-  // Only get the IDs of crashes that occurred within the last hour (if any).
+  // We generate two lists of crash IDs. One will be the crashes within the last
+  // hour, which is included in all feedback reports. The other is all of the
+  // crash IDs from the past 120 days, which is only included in feedback
+  // reports sent from @google.com accounts.
   std::vector<UploadList::UploadInfo> crashes;
   crash_upload_list_->GetUploads(kMaxCrashesCountToRetrieve, &crashes);
   const base::Time now = base::Time::Now();
   crash_ids_list_.clear();
   crash_ids_list_.reserve(kMaxCrashesCountToRetrieve *
                           (kCrashIdStringSize + 2));
+  all_crash_ids_list_.clear();
+  all_crash_ids_list_.reserve(kMaxCrashesCountToRetrieve *
+                              (kCrashIdStringSize + 2));
 
   // The feedback server expects the crash IDs to be a comma-separated list.
   for (const auto& crash_info : crashes) {
@@ -64,10 +73,15 @@
         crash_info.state == UploadList::UploadInfo::State::Uploaded
             ? crash_info.upload_time
             : crash_info.capture_time;
-    if (now - report_time < kOneHourTimeDelta) {
+    base::TimeDelta time_diff = now - report_time;
+    if (time_diff < k120DaysTimeDelta) {
       const std::string& crash_id = crash_info.upload_id;
-      crash_ids_list_.append(crash_ids_list_.empty() ? crash_id
-                                                     : ", " + crash_id);
+      all_crash_ids_list_.append(all_crash_ids_list_.empty() ? crash_id
+                                                             : ", " + crash_id);
+      if (time_diff < kOneHourTimeDelta) {
+        crash_ids_list_.append(crash_ids_list_.empty() ? crash_id
+                                                       : ", " + crash_id);
+      }
     }
   }
 
@@ -77,9 +91,11 @@
   pending_requests_.clear();
 }
 
-void CrashIdsSource::RespondWithCrashIds(SysLogsSourceCallback callback) const {
+void CrashIdsSource::RespondWithCrashIds(SysLogsSourceCallback callback) {
   auto response = std::make_unique<SystemLogsResponse>();
   (*response)[feedback::FeedbackReport::kCrashReportIdsKey] = crash_ids_list_;
+  (*response)[feedback::FeedbackReport::kAllCrashReportIdsKey] =
+      all_crash_ids_list_;
 
   // We must respond anyways.
   std::move(callback).Run(std::move(response));
diff --git a/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.h b/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.h
index 8da0956..e8e4166 100644
--- a/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.h
+++ b/chrome/browser/feedback/system_logs/log_sources/crash_ids_source.h
@@ -24,12 +24,14 @@
 
  private:
   void OnUploadListAvailable();
-  void RespondWithCrashIds(SysLogsSourceCallback callback) const;
+  void RespondWithCrashIds(SysLogsSourceCallback callback);
 
   scoped_refptr<UploadList> crash_upload_list_;
 
-  // A comma-separated list of crash IDs as expected by the server.
+  // A comma-separated list of crash IDs as expected by the server. The first
+  // is for the last hour, the second is for the pat 120 days.
   std::string crash_ids_list_;
+  std::string all_crash_ids_list_;
 
   // Contains any pending fetch requests waiting for the crash upload list to
   // finish loading.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 6ab8b45..d525b62 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1749,6 +1749,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-tab-engagement-reporting",
+    "owners": [ "memex-team@google.com" ],
+    "expiry_milestone": 82 
+  },
+  {
     "name": "enable-text-fragment-anchor",
     "owners": [ "bokan", "input-dev" ],
     "expiry_milestone": 77
@@ -1809,8 +1814,8 @@
     "expiry_milestone": 76
   },
   {
-    "name": "enable-viz-hit-test-draw-quad",
-    "owners": [ "riajiang", "rjkroege" ],
+    "name": "enable-viz-hit-test",
+    "owners": [ "riajiang", "sunxd", "rjkroege" ],
     "expiry_milestone": 76
   },
   {
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index d21623d..0eb98eb 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -800,10 +800,10 @@
     "If enabled, the display compositor runs as part of the viz service in the"
     "GPU process.";
 
-const char kVizHitTestDrawQuadName[] = "Viz Hit-test Draw-quad version";
-const char kVizHitTestDrawQuadDescription[] =
+const char kVizHitTestName[] = "Viz Hit-test";
+const char kVizHitTestDescription[] =
     "If enabled, event targeting uses the new viz-assisted hit-testing logic, "
-    "with hit-test data computed from the CompositorFrame.";
+    "with hit-test data computed from the CompositorFrame or the SurfaceLayer.";
 
 const char kCompositorThreadedScrollbarScrollingName[] =
     "Enable compositor threaded scrollbar scrolling";
@@ -1908,6 +1908,10 @@
     "keyboard shortcuts and have the events routed directly to the website "
     "when in fullscreen mode.";
 
+const char kTabEngagementReportingName[] = "Tab Engagement Metrics";
+const char kTabEngagementReportingDescription[] =
+    "Tracks tab engagement and lifetime metrics.";
+
 const char kTabGridLayoutAndroidName[] = "Tab Grid Layout";
 const char kTabGridLayoutAndroidDescription[] =
     "Allows users to see their tabs in a grid layout in the tab switcher.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ef3b0dc8..7137fbc 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -472,8 +472,8 @@
 extern const char kVizDisplayCompositorName[];
 extern const char kVizDisplayCompositorDescription[];
 
-extern const char kVizHitTestDrawQuadName[];
-extern const char kVizHitTestDrawQuadDescription[];
+extern const char kVizHitTestName[];
+extern const char kVizHitTestDescription[];
 
 extern const char kMemlogName[];
 extern const char kMemlogDescription[];
@@ -1134,6 +1134,9 @@
 extern const char kSyncUSSAutofillWalletMetadataName[];
 extern const char kSyncUSSAutofillWalletMetadataDescription[];
 
+extern const char kTabEngagementReportingName[];
+extern const char kTabEngagementReportingDescription[];
+
 extern const char kTabGridLayoutAndroidName[];
 extern const char kTabGridLayoutAndroidDescription[];
 
diff --git a/chrome/browser/metrics/perf/heap_collector.cc b/chrome/browser/metrics/perf/heap_collector.cc
index 72b942db..b0a07b3 100644
--- a/chrome/browser/metrics/perf/heap_collector.cc
+++ b/chrome/browser/metrics/perf/heap_collector.cc
@@ -171,7 +171,7 @@
     // The devices major / minor values and the inode are not filled by
     // ParseProcMaps, so write them as zero values. They are not relevant for
     // symbolization.
-    std::string row = base::StringPrintf("%08" PRIx64 "-%08" PRIx64
+    std::string row = base::StringPrintf("%08" PRIxPTR "-%08" PRIxPTR
                                          " %c%c%c%c %08llx 00:00 0 %s\n",
                                          region.start, region.end, r, w, x, p,
                                          region.offset, region.path.c_str());
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter.cc b/chrome/browser/metrics/process_memory_metrics_emitter.cc
index d83d9a78c..9b53109 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter.cc
@@ -764,7 +764,7 @@
     process_info.launch_time = process_node->launch_time();
 
     base::flat_set<performance_manager::PageNodeImpl*> page_nodes =
-        process_node->GetAssociatedPageCoordinationUnits();
+        process_node->GetAssociatedPageNodes();
     for (performance_manager::PageNodeImpl* page_node : page_nodes) {
       if (page_node->ukm_source_id() == ukm::kInvalidSourceId)
         continue;
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index f604984..3b8f6ce 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -399,9 +399,11 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     PDFExtensionTest::SetUpCommandLine(command_line);
     if (GetParam()) {
-      feature_list_.InitAndEnableFeature(features::kEnableVizHitTestDrawQuad);
+      std::map<std::string, std::string> parameters{{"provider", "draw_quad"}};
+      feature_list_.InitAndEnableFeatureWithParameters(
+          features::kEnableVizHitTest, parameters);
     } else {
-      feature_list_.InitAndDisableFeature(features::kEnableVizHitTestDrawQuad);
+      feature_list_.InitAndDisableFeature(features::kEnableVizHitTest);
     }
   }
 
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc
new file mode 100644
index 0000000..2ba4c093
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.cc
@@ -0,0 +1,223 @@
+// 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/performance_manager/decorators/frozen_frame_aggregator.h"
+
+#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
+#include "chrome/browser/performance_manager/graph/node_attached_data_impl.h"
+#include "chrome/browser/performance_manager/graph/page_node_impl.h"
+#include "chrome/browser/performance_manager/graph/process_node_impl.h"
+
+namespace performance_manager {
+
+using LifecycleState = resource_coordinator::mojom::LifecycleState;
+
+// Provides FrozenFrameAggregator machinery access to some internals of a
+// PageNodeImpl and ProcessNodeImpl.
+class FrozenFrameAggregatorAccess {
+ public:
+  using StorageType = decltype(PageNodeImpl::frozen_frame_data_);
+
+  static StorageType* GetInternalStorage(PageNodeImpl* page_node) {
+    return &page_node->frozen_frame_data_;
+  }
+
+  static StorageType* GetInternalStorage(ProcessNodeImpl* process_node) {
+    return &process_node->frozen_frame_data_;
+  }
+
+  static void SetLifecycleState(PageNodeImpl* page_node,
+                                LifecycleState lifecycle_state) {
+    page_node->SetLifecycleState(lifecycle_state);
+  }
+
+  static void NotifyAllFramesInProcessFrozen(ProcessNodeImpl* process_node) {
+    for (auto& observer : process_node->observers())
+      observer.OnAllFramesInProcessFrozen(process_node);
+  }
+};
+
+namespace {
+
+// Private implementation of the node attached data. This keeps the complexity
+// out of the header file.
+class FrozenDataImpl : public FrozenFrameAggregator::Data,
+                       public NodeAttachedDataImpl<FrozenDataImpl> {
+ public:
+  using StorageType = FrozenFrameAggregatorAccess::StorageType;
+
+  // This data is tracked persistently for page and process nodes, so uses
+  // internal node storage.
+  struct Traits : public NodeAttachedDataInternalOnNodeType<PageNodeImpl>,
+                  public NodeAttachedDataInternalOnNodeType<ProcessNodeImpl> {};
+
+  FrozenDataImpl() = default;
+  ~FrozenDataImpl() override = default;
+
+  static StorageType* GetInternalStorage(PageNodeImpl* page_node) {
+    return FrozenFrameAggregatorAccess::GetInternalStorage(page_node);
+  }
+
+  static StorageType* GetInternalStorage(ProcessNodeImpl* process_node) {
+    return FrozenFrameAggregatorAccess::GetInternalStorage(process_node);
+  }
+
+  // Returns the current "is_frozen" state. A collection of frames is considered
+  // frozen if its non-empty, and all of the frames are frozen.
+  bool IsFrozen() const {
+    return current_frame_count > 0 && frozen_frame_count == current_frame_count;
+  }
+
+  // Returns the state as an equivalent LifecycleState.
+  LifecycleState AsLifecycleState() const {
+    if (IsFrozen())
+      return LifecycleState::kFrozen;
+    return LifecycleState::kRunning;
+  }
+
+  // Applies a change to frame counts. Returns true if that causes the frozen
+  // state to change for this object.
+  bool ChangeFrameCounts(int32_t current_frame_delta,
+                         int32_t frozen_frame_delta) {
+    DCHECK(current_frame_delta != 0 || frozen_frame_delta != 0);
+    DCHECK_GE(1, abs(current_frame_delta));
+    DCHECK_GE(1, abs(frozen_frame_delta));
+    // We should never have (-1, 1) or (1, -1).
+    DCHECK_NE(-current_frame_delta, frozen_frame_delta);
+
+    // If the deltas are negative, the counts need to be positive.
+    DCHECK(current_frame_delta >= 0 || current_frame_count > 0);
+    DCHECK(frozen_frame_delta >= 0 || frozen_frame_count > 0);
+
+    bool was_frozen = IsFrozen();
+    current_frame_count += current_frame_delta;
+    frozen_frame_count += frozen_frame_delta;
+
+    return IsFrozen() != was_frozen;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrozenDataImpl);
+};
+
+bool IsFrozen(const FrameNodeImpl* frame_node) {
+  return frame_node->lifecycle_state() == LifecycleState::kFrozen;
+}
+
+}  // namespace
+
+FrozenFrameAggregator::FrozenFrameAggregator() = default;
+FrozenFrameAggregator::~FrozenFrameAggregator() = default;
+
+bool FrozenFrameAggregator::ShouldObserve(const NodeBase* node) {
+  // Use the ShouldObserve hook to ensure page and process node attached data
+  // is initialized. There's no need to observe these nodes beyond that.
+  switch (node->id().type) {
+    case resource_coordinator::CoordinationUnitType::kFrame:
+      return true;
+
+    case resource_coordinator::CoordinationUnitType::kPage: {
+      auto* page_node = PageNodeImpl::FromNodeBase(node);
+      // Expect a page to always start in the running state.
+      DCHECK_EQ(LifecycleState::kRunning, page_node->lifecycle_state());
+      FrozenDataImpl::GetOrCreate(page_node);
+      return false;
+    }
+
+    case resource_coordinator::CoordinationUnitType::kProcess: {
+      FrozenDataImpl::GetOrCreate(ProcessNodeImpl::FromNodeBase(node));
+      return false;
+    }
+
+    default:
+      return false;
+  }
+  NOTREACHED();
+}
+
+void FrozenFrameAggregator::OnNodeAdded(NodeBase* node) {
+  // We only observe frame nodes.
+  DCHECK_EQ(resource_coordinator::CoordinationUnitType::kFrame,
+            node->id().type);
+
+  auto* frame_node = FrameNodeImpl::FromNodeBase(node);
+  DCHECK(!IsFrozen(frame_node));  // A newly created node can never be frozen.
+  AddOrRemoveFrame(frame_node, 1);
+}
+
+void FrozenFrameAggregator::OnBeforeNodeRemoved(NodeBase* node) {
+  if (node->id().type != resource_coordinator::CoordinationUnitType::kFrame)
+    return;
+
+  auto* frame_node = FrameNodeImpl::FromNodeBase(node);
+  AddOrRemoveFrame(frame_node, -1);
+}
+
+void FrozenFrameAggregator::OnIsCurrentChanged(FrameNodeImpl* frame_node) {
+  int32_t current_frame_delta = frame_node->is_current() ? 1 : -1;
+  int32_t frozen_frame_delta = IsFrozen(frame_node) ? current_frame_delta : 0;
+  UpdateFrameCounts(frame_node, current_frame_delta, frozen_frame_delta);
+}
+
+void FrozenFrameAggregator::OnLifecycleStateChanged(FrameNodeImpl* frame_node) {
+  if (!frame_node->is_current())
+    return;
+  int32_t frozen_frame_delta = IsFrozen(frame_node) ? 1 : -1;
+  UpdateFrameCounts(frame_node, 0, frozen_frame_delta);
+}
+
+void FrozenFrameAggregator::AddOrRemoveFrame(FrameNodeImpl* frame_node,
+                                             int32_t delta) {
+  int32_t current_frame_delta = 0;
+  int32_t frozen_frame_delta = 0;
+  if (frame_node->is_current()) {
+    current_frame_delta = delta;
+    if (IsFrozen(frame_node))
+      frozen_frame_delta = delta;
+  }
+
+  UpdateFrameCounts(frame_node, current_frame_delta, frozen_frame_delta);
+}
+
+void FrozenFrameAggregator::UpdateFrameCounts(FrameNodeImpl* frame_node,
+                                              int32_t current_frame_delta,
+                                              int32_t frozen_frame_delta) {
+  // If a non-current frame is added or removed the deltas can be zero. In this
+  // case the logic can be aborted early to save some effort.
+  if (current_frame_delta == 0 && frozen_frame_delta == 0)
+    return;
+
+  auto* page_node = frame_node->page_node();
+  auto* process_node = frame_node->process_node();
+  auto* page_data = FrozenDataImpl::Get(page_node);
+  auto* process_data = FrozenDataImpl::Get(process_node);
+
+  // Set the page lifecycle state based on the state of the frame tree.
+  if (page_data->ChangeFrameCounts(current_frame_delta, frozen_frame_delta)) {
+    FrozenFrameAggregatorAccess::SetLifecycleState(
+        page_node, page_data->AsLifecycleState());
+  }
+
+  // Update the process state, and notify when all frames in the tree are
+  // frozen.
+  if (process_data->ChangeFrameCounts(current_frame_delta,
+                                      frozen_frame_delta) &&
+      process_data->IsFrozen()) {
+    FrozenFrameAggregatorAccess::NotifyAllFramesInProcessFrozen(process_node);
+  }
+}
+
+// static
+FrozenFrameAggregator::Data* FrozenFrameAggregator::Data::GetForTesting(
+    PageNodeImpl* page_node) {
+  return FrozenDataImpl::Get(page_node);
+}
+
+// static
+FrozenFrameAggregator::Data* FrozenFrameAggregator::Data::GetForTesting(
+    ProcessNodeImpl* process_node) {
+  return FrozenDataImpl::Get(process_node);
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h
new file mode 100644
index 0000000..84d692e
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h
@@ -0,0 +1,69 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_FROZEN_FRAME_AGGREGATOR_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_FROZEN_FRAME_AGGREGATOR_H_
+
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
+
+namespace performance_manager {
+
+class NodeBase;
+class FrameNodeImpl;
+class PageNodeImpl;
+class ProcessNodeImpl;
+
+// The FrozenFrameAggregator is responsible for tracking frame frozen states,
+// and aggregating this property to the page and process nodes.
+class FrozenFrameAggregator : public GraphObserver {
+ public:
+  struct Data;
+
+  // TODO(chrisha): Check that the graph is empty when this observer is added!
+  // https://www.crbug.com/952891
+  FrozenFrameAggregator();
+  ~FrozenFrameAggregator() override;
+
+  // GraphObserver implementation:
+  bool ShouldObserve(const NodeBase* node) override;
+  void OnNodeAdded(NodeBase* node) override;
+  void OnBeforeNodeRemoved(NodeBase* node) override;
+  void OnIsCurrentChanged(FrameNodeImpl* frame_node) override;
+  void OnLifecycleStateChanged(FrameNodeImpl* page_node) override;
+
+ protected:
+  friend class FrozenFrameAggregatorTest;
+
+  // Used to update counts when adding or removing a |frame_node|. A |delta| of
+  // -1 indicates a removal, while +1 indicates adding.
+  void AddOrRemoveFrame(FrameNodeImpl* frame_node, int32_t delta);
+
+  // Updates the frame counts associated with the given |frame_node|. Takes
+  // care of updating page and process state, as well as firing any needed
+  // notifications.
+  void UpdateFrameCounts(FrameNodeImpl* frame_node,
+                         int32_t current_frame_delta,
+                         int32_t frozen_frame_delta);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrozenFrameAggregator);
+};
+
+// This struct is stored internally on page and process nodes using
+// InlineNodeAttachedDataStorage.
+struct FrozenFrameAggregator::Data {
+  // The number of current frames associated with a given page/process.
+  uint32_t current_frame_count = 0;
+
+  // The number of frozen current frames associated with a given page/process.
+  // This is always <= |current_frame_count|.
+  uint32_t frozen_frame_count = 0;
+
+  static Data* GetForTesting(PageNodeImpl* page_node);
+  static Data* GetForTesting(ProcessNodeImpl* process_node);
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_DECORATORS_FROZEN_FRAME_AGGREGATOR_H_
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
new file mode 100644
index 0000000..44efe502
--- /dev/null
+++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
@@ -0,0 +1,279 @@
+// 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/performance_manager/decorators/frozen_frame_aggregator.h"
+
+#include <memory>
+
+#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
+#include "chrome/browser/performance_manager/graph/graph_test_harness.h"
+#include "chrome/browser/performance_manager/graph/page_node_impl.h"
+#include "chrome/browser/performance_manager/graph/process_node_impl.h"
+#include "chrome/browser/performance_manager/observers/graph_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+
+namespace {
+
+using LifecycleState = PageNodeImpl::LifecycleState;
+
+class LenientMockGraphObserver : public GraphObserver {
+ public:
+  LenientMockGraphObserver() = default;
+  ~LenientMockGraphObserver() override = default;
+
+  virtual bool ShouldObserve(const NodeBase* node) { return false; }
+
+  MOCK_METHOD1(OnAllFramesInProcessFrozen, void(ProcessNodeImpl*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(LenientMockGraphObserver);
+};
+
+using MockGraphObserver = ::testing::StrictMock<LenientMockGraphObserver>;
+
+}  // namespace
+
+class FrozenFrameAggregatorTest : public GraphTestHarness {
+ protected:
+  FrozenFrameAggregatorTest() = default;
+  ~FrozenFrameAggregatorTest() override = default;
+
+  void SetUp() override {
+    ffa_ = std::make_unique<FrozenFrameAggregator>();
+    graph()->RegisterObserver(ffa_.get());
+    process_node_ = CreateNode<ProcessNodeImpl>();
+    page_node_ = CreateNode<PageNodeImpl>(nullptr);
+  }
+
+  void TearDown() override { graph()->UnregisterObserver(ffa_.get()); }
+
+  template <typename NodeType>
+  void ExpectData(NodeType* node,
+                  uint32_t current_frame_count,
+                  uint32_t frozen_frame_count) {
+    auto* data = FrozenFrameAggregator::Data::GetForTesting(node);
+    EXPECT_TRUE(data);
+    EXPECT_EQ(current_frame_count, data->current_frame_count);
+    EXPECT_EQ(frozen_frame_count, data->frozen_frame_count);
+  }
+
+  void ExpectPageData(uint32_t current_frame_count,
+                      uint32_t frozen_frame_count) {
+    ExpectData(page_node_.get(), current_frame_count, frozen_frame_count);
+  }
+
+  void ExpectProcessData(uint32_t current_frame_count,
+                         uint32_t frozen_frame_count) {
+    ExpectData(process_node_.get(), current_frame_count, frozen_frame_count);
+  }
+
+  void ExpectRunning() {
+    EXPECT_EQ(LifecycleState::kRunning, page_node_.get()->lifecycle_state());
+  }
+
+  void ExpectFrozen() {
+    EXPECT_EQ(LifecycleState::kFrozen, page_node_.get()->lifecycle_state());
+  }
+
+  TestNodeWrapper<FrameNodeImpl> CreateFrame(FrameNodeImpl* parent_frame_node,
+                                             int frame_tree_node_id) {
+    return CreateNode<FrameNodeImpl>(process_node_.get(), page_node_.get(),
+                                     parent_frame_node, frame_tree_node_id);
+  }
+
+  std::unique_ptr<FrozenFrameAggregator> ffa_;
+  TestNodeWrapper<ProcessNodeImpl> process_node_;
+  TestNodeWrapper<PageNodeImpl> page_node_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FrozenFrameAggregatorTest);
+};
+
+TEST_F(FrozenFrameAggregatorTest, ProcessAggregation) {
+  // Explicitly add the observer to only the process node.
+  MockGraphObserver obs;
+  process_node_.get()->AddObserver(&obs);
+
+  ExpectProcessData(0, 0);
+
+  // Add a main frame.
+  auto f0 = CreateFrame(nullptr, 0);
+  ExpectProcessData(0, 0);
+
+  // Make the frame current.
+  f0.get()->SetIsCurrent(true);
+  ExpectProcessData(1, 0);
+
+  // Make the frame frozen and expect a notification.
+  EXPECT_CALL(obs, OnAllFramesInProcessFrozen(process_node_.get()));
+  f0.get()->SetLifecycleState(LifecycleState::kFrozen);
+  testing::Mock::VerifyAndClear(&obs);
+  ExpectProcessData(1, 1);
+
+  // Create another process and another page.
+  auto proc2 = CreateNode<ProcessNodeImpl>();
+  auto page2 = CreateNode<PageNodeImpl>(nullptr);
+  ExpectProcessData(1, 1);
+
+  // Create a child frame for the first page hosted in the second process.
+  auto f1 =
+      CreateNode<FrameNodeImpl>(proc2.get(), page_node_.get(), f0.get(), 1);
+  ExpectProcessData(1, 1);
+
+  // Immediately make it current.
+  f1.get()->SetIsCurrent(true);
+  ExpectProcessData(1, 1);
+
+  // Freeze the child frame and expect no change, as its in another process.
+  f1.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectProcessData(1, 1);
+
+  // Unfreeze both frames.
+  f0.get()->SetLifecycleState(LifecycleState::kRunning);
+  ExpectProcessData(1, 0);
+  f1.get()->SetLifecycleState(LifecycleState::kRunning);
+  ExpectProcessData(1, 0);
+
+  // Create a main frame in the second page, but that's in the first process.
+  auto f2 =
+      CreateNode<FrameNodeImpl>(process_node_.get(), page2.get(), nullptr, 2);
+  ExpectProcessData(1, 0);
+
+  // Freeze the main frame in the second page.
+  f2.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectProcessData(1, 0);
+
+  // Make the frozen second main frame current.
+  f2.get()->SetIsCurrent(true);
+  ExpectProcessData(2, 1);
+
+  // Freeze the child frame of the first page, hosted in the other process.
+  f1.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectProcessData(2, 1);
+
+  // Freeze the main frame of the first page.
+  EXPECT_CALL(obs, OnAllFramesInProcessFrozen(process_node_.get()));
+  f0.get()->SetLifecycleState(LifecycleState::kFrozen);
+  testing::Mock::VerifyAndClear(&obs);
+  ExpectProcessData(2, 2);
+
+  // Destroy the child frame in the other process, and then kill that process.
+  f1.reset();
+  ExpectProcessData(2, 2);
+  proc2.reset();
+  ExpectProcessData(2, 2);
+
+  // Kill the main frame of the second page.
+  f2.reset();
+  ExpectProcessData(1, 1);
+
+  // Kill the main frame of the first page.
+  f0.reset();
+  ExpectProcessData(0, 0);
+
+  process_node_.get()->RemoveObserver(&obs);
+}
+
+TEST_F(FrozenFrameAggregatorTest, PageAggregation) {
+  ExpectPageData(0, 0);
+  ExpectRunning();
+
+  // Add a non-current frame.
+  auto f0 = CreateFrame(nullptr, 0);
+  ExpectPageData(0, 0);
+  ExpectRunning();
+
+  // Make the frame current.
+  f0.get()->SetIsCurrent(true);
+  ExpectPageData(1, 0);
+  ExpectRunning();
+
+  // Freeze the frame.
+  f0.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectPageData(1, 1);
+  ExpectFrozen();
+
+  // Unfreeze the frame.
+  f0.get()->SetLifecycleState(LifecycleState::kRunning);
+  ExpectPageData(1, 0);
+  ExpectRunning();
+
+  // Add a child frame.
+  auto f1 = CreateFrame(f0.get(), 1);
+  ExpectPageData(1, 0);
+  ExpectRunning();
+
+  // Make it current as well.
+  f1.get()->SetIsCurrent(true);
+  ExpectPageData(2, 0);
+  ExpectRunning();
+
+  // Freeze them both.
+  f1.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectPageData(2, 1);
+  ExpectRunning();
+  f0.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectPageData(2, 2);
+  ExpectFrozen();
+
+  // Unfreeze them both.
+  f0.get()->SetLifecycleState(LifecycleState::kRunning);
+  ExpectPageData(2, 1);
+  ExpectRunning();
+  f1.get()->SetLifecycleState(LifecycleState::kRunning);
+  ExpectPageData(2, 0);
+  ExpectRunning();
+
+  // Create a third frame.
+  auto f1a = CreateFrame(f0.get(), 1);
+  ExpectPageData(2, 0);
+  ExpectRunning();
+
+  // Swap the f1 and f1a.
+  f1.get()->SetIsCurrent(false);
+  ExpectPageData(1, 0);
+  ExpectRunning();
+  f1a.get()->SetIsCurrent(true);
+  ExpectPageData(2, 0);
+  ExpectRunning();
+
+  // Freeze the original frame and swap it back.
+  f1.get()->SetLifecycleState(LifecycleState::kFrozen);
+  f1a.get()->SetIsCurrent(false);
+  ExpectPageData(1, 0);
+  ExpectRunning();
+  f1.get()->SetIsCurrent(true);
+  ExpectPageData(2, 1);
+  ExpectRunning();
+
+  // Freeze the non-current frame and expect nothing to change.
+  f1a.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectPageData(2, 1);
+  ExpectRunning();
+
+  // Remove the non-current frame and expect nothing to change.
+  f1a.reset();
+  ExpectPageData(2, 1);
+  ExpectRunning();
+
+  // Remove the frozen child frame and expect a change.
+  f1.reset();
+  ExpectPageData(1, 0);
+  ExpectRunning();
+
+  // Freeze the main frame again.
+  f0.get()->SetLifecycleState(LifecycleState::kFrozen);
+  ExpectPageData(1, 1);
+  ExpectFrozen();
+
+  // Remove the main frame. An empty page is always considered as "running".
+  f0.reset();
+  ExpectPageData(0, 0);
+  ExpectRunning();
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.cc b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.cc
index ed02833..0587f23 100644
--- a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.cc
+++ b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.cc
@@ -74,21 +74,6 @@
   NOTREACHED();
 }
 
-void PageAlmostIdleDecorator::OnPageEventReceived(
-    PageNodeImpl* page_node,
-    resource_coordinator::mojom::Event event) {
-  // Only the navigation committed event is of interest.
-  if (event != resource_coordinator::mojom::Event::kNavigationCommitted)
-    return;
-
-  // Reset the load-idle state associated with this page as a new navigation has
-  // started.
-  auto* data = DataImpl::GetOrCreate(page_node);
-  data->load_idle_state_ = LoadIdleState::kLoadingNotStarted;
-  PageAlmostIdleAccess::SetPageAlmostIdle(page_node, false);
-  UpdateLoadIdleStatePage(page_node);
-}
-
 void PageAlmostIdleDecorator::OnNetworkAlmostIdleChanged(
     FrameNodeImpl* frame_node) {
   UpdateLoadIdleStateFrame(frame_node);
@@ -98,6 +83,16 @@
   UpdateLoadIdleStatePage(page_node);
 }
 
+void PageAlmostIdleDecorator::OnMainFrameNavigationCommitted(
+    PageNodeImpl* page_node) {
+  // Reset the load-idle state associated with this page as a new navigation has
+  // started.
+  auto* data = DataImpl::GetOrCreate(page_node);
+  data->load_idle_state_ = LoadIdleState::kLoadingNotStarted;
+  PageAlmostIdleAccess::SetPageAlmostIdle(page_node, false);
+  UpdateLoadIdleStatePage(page_node);
+}
+
 void PageAlmostIdleDecorator::OnMainThreadTaskLoadIsLow(
     ProcessNodeImpl* process_node) {
   UpdateLoadIdleStateProcess(process_node);
diff --git a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
index 847af6a..4d03ae5 100644
--- a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
+++ b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h
@@ -28,10 +28,9 @@
 
   // GraphObserver implementation:
   bool ShouldObserve(const NodeBase* node) override;
-  void OnPageEventReceived(PageNodeImpl* page_node,
-                           resource_coordinator::mojom::Event event) override;
   void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) override;
   void OnIsLoadingChanged(PageNodeImpl* page_node) override;
+  void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) override;
   void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) override;
 
  protected:
diff --git a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc
index 44ab8f1..0c8ffc7 100644
--- a/chrome/browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc
+++ b/chrome/browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc
@@ -22,35 +22,6 @@
 
 namespace performance_manager {
 
-namespace {
-
-class LenientMockGraphObserver : public GraphObserver {
- public:
-  LenientMockGraphObserver() = default;
-  ~LenientMockGraphObserver() override = default;
-
-  virtual bool ShouldObserve(const NodeBase* node) {
-    return node->id().type == PageNodeImpl::Type();
-  }
-
-  MOCK_METHOD1(OnPageAlmostIdleChanged, void(PageNodeImpl*));
-
-  void ExpectOnPageAlmostIdleChanged(PageNodeImpl* page_node,
-                                     bool page_almost_idle) {
-    EXPECT_CALL(*this, OnPageAlmostIdleChanged(page_node))
-        .WillOnce(::testing::InvokeWithoutArgs([page_node, page_almost_idle]() {
-          EXPECT_EQ(page_almost_idle, page_node->page_almost_idle());
-        }));
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(LenientMockGraphObserver);
-};
-
-using MockGraphObserver = ::testing::StrictMock<LenientMockGraphObserver>;
-
-}  // namespace
-
 class PageAlmostIdleDecoratorTest : public GraphTestHarness {
  protected:
   PageAlmostIdleDecoratorTest() = default;
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.cc b/chrome/browser/performance_manager/graph/frame_node_impl.cc
index 11629b81..b84f639 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.cc
@@ -37,15 +37,7 @@
 void FrameNodeImpl::SetLifecycleState(
     resource_coordinator::mojom::LifecycleState state) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (state == lifecycle_state_)
-    return;
-
-  resource_coordinator::mojom::LifecycleState old_state = lifecycle_state_;
-  lifecycle_state_ = state;
-
-  // Notify parents of this change.
-  process_node_->OnFrameLifecycleStateChanged(this, old_state);
-  page_node_->OnFrameLifecycleStateChanged(this, old_state);
+  lifecycle_state_.SetAndMaybeNotify(this, state);
 }
 
 void FrameNodeImpl::SetHasNonEmptyBeforeUnload(bool has_nonempty_beforeunload) {
@@ -81,8 +73,9 @@
 }
 
 void FrameNodeImpl::OnNonPersistentNotificationCreated() {
-  SendEvent(
-      resource_coordinator::mojom::Event::kNonPersistentNotificationCreated);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& observer : observers())
+    observer.OnNonPersistentNotificationCreated(this);
 }
 
 FrameNodeImpl* FrameNodeImpl::parent_frame_node() const {
@@ -109,7 +102,7 @@
 resource_coordinator::mojom::LifecycleState FrameNodeImpl::lifecycle_state()
     const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return lifecycle_state_;
+  return lifecycle_state_.value();
 }
 
 bool FrameNodeImpl::has_nonempty_beforeunload() const {
@@ -265,12 +258,6 @@
   process_node_->RemoveFrame(this);
 }
 
-void FrameNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (auto& observer : observers())
-    observer.OnFrameEventReceived(this, event);
-}
-
 bool FrameNodeImpl::HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (parent_frame_node_ == frame_node ||
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl.h b/chrome/browser/performance_manager/graph/frame_node_impl.h
index f45eb5ab..bbbb0df 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl.h
+++ b/chrome/browser/performance_manager/graph/frame_node_impl.h
@@ -111,9 +111,6 @@
   void JoinGraph() override;
   void LeaveGraph() override;
 
-  // CoordinationUnitInterface implementation.
-  void OnEventReceived(resource_coordinator::mojom::Event event) override;
-
   bool HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const;
   bool HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const;
 
@@ -125,8 +122,11 @@
   const int frame_tree_node_id_;
 
   base::flat_set<FrameNodeImpl*> child_frame_nodes_;
-  resource_coordinator::mojom::LifecycleState lifecycle_state_ =
-      resource_coordinator::mojom::LifecycleState::kRunning;
+  ObservedProperty::NotifiesOnlyOnChanges<
+      resource_coordinator::mojom::LifecycleState,
+      &GraphObserver::OnLifecycleStateChanged>
+      lifecycle_state_{resource_coordinator::mojom::LifecycleState::kRunning};
+
   bool has_nonempty_beforeunload_ = false;
   GURL url_;
 
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
index 9e01e6cd..fb0597b 100644
--- a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
@@ -38,7 +38,7 @@
 
 TEST_F(FrameNodeImplTest, AddFrameHierarchyBasic) {
   auto process = CreateNode<ProcessNodeImpl>();
-  auto page = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page = CreateNode<PageNodeImpl>(nullptr);
   auto parent_node =
       CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 0);
   auto child2_node = CreateNode<FrameNodeImpl>(process.get(), page.get(),
@@ -54,7 +54,7 @@
 
 TEST_F(FrameNodeImplTest, Url) {
   auto process = CreateNode<ProcessNodeImpl>();
-  auto page = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page = CreateNode<PageNodeImpl>(nullptr);
   auto frame_node =
       CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 0);
   EXPECT_TRUE(frame_node->url().is_empty());
@@ -65,7 +65,7 @@
 
 TEST_F(FrameNodeImplTest, RemoveChildFrame) {
   auto process = CreateNode<ProcessNodeImpl>();
-  auto page = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page = CreateNode<PageNodeImpl>(nullptr);
   auto parent_frame_node =
       CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 0);
   auto child_frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get(),
@@ -84,68 +84,4 @@
   EXPECT_TRUE(!parent_frame_node->parent_frame_node());
 }
 
-TEST_F(FrameNodeImplTest, LifecycleStatesTransitions) {
-  const auto kRunning = resource_coordinator::mojom::LifecycleState::kRunning;
-  const auto kFrozen = resource_coordinator::mojom::LifecycleState::kFrozen;
-  MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
-
-  // Verifying the model.
-  ASSERT_TRUE(mock_graph.frame->IsMainFrame());
-  ASSERT_TRUE(mock_graph.other_frame->IsMainFrame());
-  ASSERT_FALSE(mock_graph.child_frame->IsMainFrame());
-  ASSERT_EQ(mock_graph.child_frame->parent_frame_node(),
-            mock_graph.other_frame.get());
-  ASSERT_EQ(mock_graph.frame->page_node(), mock_graph.page.get());
-  ASSERT_EQ(mock_graph.other_frame->page_node(), mock_graph.other_page.get());
-
-  // Freezing a child frame should not affect the page state.
-  mock_graph.child_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-
-  // Freezing the only frame in a page should freeze that page.
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-
-  // Unfreeze the child frame in the other page.
-  mock_graph.child_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kRunning);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-
-  // Freezing the main frame in the other page should not alter that pages
-  // state, as there is still a child frame that is running.
-  mock_graph.other_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-
-  // Refreezing the child frame should freeze the page.
-  mock_graph.child_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kFrozen, mock_graph.other_page->lifecycle_state());
-
-  // Unfreezing a main frame should unfreeze the associated page.
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kRunning);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kFrozen, mock_graph.other_page->lifecycle_state());
-
-  // Unfreezing the child frame should unfreeze the associated page.
-  mock_graph.child_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kRunning);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-
-  // Unfreezing the main frame shouldn't change anything.
-  mock_graph.other_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kRunning);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-  EXPECT_EQ(kRunning, mock_graph.other_page->lifecycle_state());
-}
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/graph_test_harness.h b/chrome/browser/performance_manager/graph/graph_test_harness.h
index 90ea2f1..f221655 100644
--- a/chrome/browser/performance_manager/graph/graph_test_harness.h
+++ b/chrome/browser/performance_manager/graph/graph_test_harness.h
@@ -34,16 +34,22 @@
     return TestNodeWrapper<NodeClass>(std::move(node));
   }
 
+  TestNodeWrapper() {}
+
   explicit TestNodeWrapper(std::unique_ptr<NodeClass> impl)
       : impl_(std::move(impl)) {
     DCHECK(impl_.get());
   }
+
+  TestNodeWrapper(TestNodeWrapper&& other) : impl_(std::move(other.impl_)) {}
+
+  void operator=(TestNodeWrapper&& other) { impl_ = std::move(other.impl_); }
+  void operator=(TestNodeWrapper& other) = delete;
+
   ~TestNodeWrapper() { reset(); }
 
   NodeClass* operator->() const { return impl_.get(); }
 
-  TestNodeWrapper(TestNodeWrapper&& other) : impl_(std::move(other.impl_)) {}
-
   NodeClass* get() const { return impl_.get(); }
 
   void reset() {
@@ -55,8 +61,6 @@
 
  private:
   std::unique_ptr<NodeClass> impl_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestNodeWrapper);
 };
 
 // This specialization is necessary because the graph has ownership of the
diff --git a/chrome/browser/performance_manager/graph/mock_graphs.cc b/chrome/browser/performance_manager/graph/mock_graphs.cc
index a84f57c..4967aea 100644
--- a/chrome/browser/performance_manager/graph/mock_graphs.cc
+++ b/chrome/browser/performance_manager/graph/mock_graphs.cc
@@ -31,7 +31,7 @@
     Graph* graph)
     : system(TestNodeWrapper<SystemNodeImpl>::Create(graph)),
       process(TestNodeWrapper<TestProcessNodeImpl>::Create(graph)),
-      page(TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr /*TEST*/)),
+      page(TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr)),
       frame(TestNodeWrapper<FrameNodeImpl>::Create(graph,
                                                    process.get(),
                                                    page.get(),
@@ -51,8 +51,7 @@
 MockMultiplePagesInSingleProcessGraph::MockMultiplePagesInSingleProcessGraph(
     Graph* graph)
     : MockSinglePageInSingleProcessGraph(graph),
-      other_page(
-          TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr /*TEST*/)),
+      other_page(TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr)),
       other_frame(TestNodeWrapper<FrameNodeImpl>::Create(graph,
                                                          process.get(),
                                                          other_page.get(),
diff --git a/chrome/browser/performance_manager/graph/node_attached_data_impl.h b/chrome/browser/performance_manager/graph/node_attached_data_impl.h
index 52c6cee..ec2c96d 100644
--- a/chrome/browser/performance_manager/graph/node_attached_data_impl.h
+++ b/chrome/browser/performance_manager/graph/node_attached_data_impl.h
@@ -389,11 +389,8 @@
 template <typename NodeType>
 DataType* NodeAttachedDataImpl<DataType>::NodeAttachedDataInternalOnNodeType<
     NodeType>::GetOrCreate(const NodeType* node) {
-  static_assert(
-      std::is_same<InternalNodeAttachedDataStorage<sizeof(DataType)>*,
-                   typename std::result_of<decltype (
-                       &DataType::GetInternalStorage)(NodeType*)>::type>::value,
-      "NodeType provided internal storage doesn't match size of DataType");
+  // TODO(chrisha): Add a compile test that this is enforced. Otherwise, there's
+  // potential for a OOB reads / security issues. https://www.crbug.com/952864
   InternalNodeAttachedDataStorage<sizeof(DataType)>* storage =
       DataType::GetInternalStorage(const_cast<NodeType*>(node));
   if (!storage->Get()) {
diff --git a/chrome/browser/performance_manager/graph/node_base.cc b/chrome/browser/performance_manager/graph/node_base.cc
index 13be796..b7c70a23 100644
--- a/chrome/browser/performance_manager/graph/node_base.cc
+++ b/chrome/browser/performance_manager/graph/node_base.cc
@@ -46,15 +46,4 @@
   return graph_->GetNodeByID(other_node->id()) == other_node;
 }
 
-void NodeBase::OnEventReceived(resource_coordinator::mojom::Event event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (auto& observer : observers())
-    observer.OnEventReceived(this, event);
-}
-
-void NodeBase::SendEvent(resource_coordinator::mojom::Event event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  OnEventReceived(event);
-}
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/node_base.h b/chrome/browser/performance_manager/graph/node_base.h
index 593ccfce..e58aa5e 100644
--- a/chrome/browser/performance_manager/graph/node_base.h
+++ b/chrome/browser/performance_manager/graph/node_base.h
@@ -62,10 +62,6 @@
   // Returns true if |other_node| is in the same graph.
   bool NodeInGraph(const NodeBase* other_node) const;
 
-  virtual void OnEventReceived(resource_coordinator::mojom::Event event);
-
-  void SendEvent(resource_coordinator::mojom::Event event);
-
   Graph* const graph_;
   const resource_coordinator::CoordinationUnitID id_;
 
diff --git a/chrome/browser/performance_manager/graph/node_base_unittest.cc b/chrome/browser/performance_manager/graph/node_base_unittest.cc
index f92ddcd..9c3890e8 100644
--- a/chrome/browser/performance_manager/graph/node_base_unittest.cc
+++ b/chrome/browser/performance_manager/graph/node_base_unittest.cc
@@ -20,90 +20,86 @@
 
 }  // namespace
 
-TEST_F(NodeBaseTest,
-       GetAssociatedCoordinationUnitsForSinglePageInSingleProcess) {
+TEST_F(NodeBaseTest, GetAssociatedNodesForSinglePageInSingleProcess) {
   MockSinglePageInSingleProcessGraph mock_graph(graph());
 
   auto pages_associated_with_process =
-      mock_graph.process->GetAssociatedPageCoordinationUnits();
+      mock_graph.process->GetAssociatedPageNodes();
   EXPECT_EQ(1u, pages_associated_with_process.size());
   EXPECT_EQ(1u, pages_associated_with_process.count(mock_graph.page.get()));
 
   auto processes_associated_with_page =
-      mock_graph.page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.page->GetAssociatedProcessNodes();
   EXPECT_EQ(1u, processes_associated_with_page.size());
   EXPECT_EQ(1u, processes_associated_with_page.count(mock_graph.process.get()));
 }
 
-TEST_F(NodeBaseTest,
-       GetAssociatedCoordinationUnitsForMultiplePagesInSingleProcess) {
+TEST_F(NodeBaseTest, GetAssociatedNodesForMultiplePagesInSingleProcess) {
   MockMultiplePagesInSingleProcessGraph mock_graph(graph());
 
   auto pages_associated_with_process =
-      mock_graph.process->GetAssociatedPageCoordinationUnits();
+      mock_graph.process->GetAssociatedPageNodes();
   EXPECT_EQ(2u, pages_associated_with_process.size());
   EXPECT_EQ(1u, pages_associated_with_process.count(mock_graph.page.get()));
   EXPECT_EQ(1u,
             pages_associated_with_process.count(mock_graph.other_page.get()));
 
   auto processes_associated_with_page =
-      mock_graph.page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.page->GetAssociatedProcessNodes();
   EXPECT_EQ(1u, processes_associated_with_page.size());
   EXPECT_EQ(1u, processes_associated_with_page.count(mock_graph.process.get()));
 
   auto processes_associated_with_other_page =
-      mock_graph.other_page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.other_page->GetAssociatedProcessNodes();
   EXPECT_EQ(1u, processes_associated_with_other_page.size());
   EXPECT_EQ(1u, processes_associated_with_page.count(mock_graph.process.get()));
 }
 
-TEST_F(NodeBaseTest,
-       GetAssociatedCoordinationUnitsForSinglePageWithMultipleProcesses) {
+TEST_F(NodeBaseTest, GetAssociatedNodesForSinglePageWithMultipleProcesses) {
   MockSinglePageWithMultipleProcessesGraph mock_graph(graph());
 
   auto pages_associated_with_process =
-      mock_graph.process->GetAssociatedPageCoordinationUnits();
+      mock_graph.process->GetAssociatedPageNodes();
   EXPECT_EQ(1u, pages_associated_with_process.size());
   EXPECT_EQ(1u, pages_associated_with_process.count(mock_graph.page.get()));
 
   auto pages_associated_with_other_process =
-      mock_graph.other_process->GetAssociatedPageCoordinationUnits();
+      mock_graph.other_process->GetAssociatedPageNodes();
   EXPECT_EQ(1u, pages_associated_with_other_process.size());
   EXPECT_EQ(1u,
             pages_associated_with_other_process.count(mock_graph.page.get()));
 
   auto processes_associated_with_page =
-      mock_graph.page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.page->GetAssociatedProcessNodes();
   EXPECT_EQ(2u, processes_associated_with_page.size());
   EXPECT_EQ(1u, processes_associated_with_page.count(mock_graph.process.get()));
   EXPECT_EQ(
       1u, processes_associated_with_page.count(mock_graph.other_process.get()));
 }
 
-TEST_F(NodeBaseTest,
-       GetAssociatedCoordinationUnitsForMultiplePagesWithMultipleProcesses) {
+TEST_F(NodeBaseTest, GetAssociatedNodesForMultiplePagesWithMultipleProcesses) {
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
 
   auto pages_associated_with_process =
-      mock_graph.process->GetAssociatedPageCoordinationUnits();
+      mock_graph.process->GetAssociatedPageNodes();
   EXPECT_EQ(2u, pages_associated_with_process.size());
   EXPECT_EQ(1u, pages_associated_with_process.count(mock_graph.page.get()));
   EXPECT_EQ(1u,
             pages_associated_with_process.count(mock_graph.other_page.get()));
 
   auto pages_associated_with_other_process =
-      mock_graph.other_process->GetAssociatedPageCoordinationUnits();
+      mock_graph.other_process->GetAssociatedPageNodes();
   EXPECT_EQ(1u, pages_associated_with_other_process.size());
   EXPECT_EQ(1u, pages_associated_with_other_process.count(
                     mock_graph.other_page.get()));
 
   auto processes_associated_with_page =
-      mock_graph.page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.page->GetAssociatedProcessNodes();
   EXPECT_EQ(1u, processes_associated_with_page.size());
   EXPECT_EQ(1u, processes_associated_with_page.count(mock_graph.process.get()));
 
   auto processes_associated_with_other_page =
-      mock_graph.other_page->GetAssociatedProcessCoordinationUnits();
+      mock_graph.other_page->GetAssociatedProcessNodes();
   EXPECT_EQ(2u, processes_associated_with_other_page.size());
   EXPECT_EQ(
       1u, processes_associated_with_other_page.count(mock_graph.process.get()));
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.cc b/chrome/browser/performance_manager/graph/page_node_impl.cc
index 1fc0f42ac..64c5a89cd 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl.cc
@@ -64,16 +64,10 @@
   DCHECK_EQ(this, frame_node->page_node());
   DCHECK(NodeInGraph(frame_node));
 
-  if (frame_node->parent_frame_node() == nullptr) {
-    main_frame_nodes_.insert(frame_node);
-  }
   ++frame_node_count_;
+  if (frame_node->parent_frame_node() == nullptr)
+    main_frame_nodes_.insert(frame_node);
 
-  OnNumFrozenFramesStateChange(
-      frame_node->lifecycle_state() ==
-              resource_coordinator::mojom::LifecycleState::kFrozen
-          ? 1
-          : 0);
   MaybeInvalidateInterventionPolicies(frame_node, true /* adding_frame */);
 }
 
@@ -89,11 +83,6 @@
     DCHECK_EQ(1u, removed);
   }
 
-  OnNumFrozenFramesStateChange(
-      frame_node->lifecycle_state() ==
-              resource_coordinator::mojom::LifecycleState::kFrozen
-          ? -1
-          : 0);
   MaybeInvalidateInterventionPolicies(frame_node, false /* adding_frame */);
 }
 
@@ -116,11 +105,15 @@
 }
 
 void PageNodeImpl::OnFaviconUpdated() {
-  SendEvent(resource_coordinator::mojom::Event::kFaviconUpdated);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& observer : observers())
+    observer.OnFaviconUpdated(this);
 }
 
 void PageNodeImpl::OnTitleUpdated() {
-  SendEvent(resource_coordinator::mojom::Event::kTitleUpdated);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& observer : observers())
+    observer.OnTitleUpdated(this);
 }
 
 void PageNodeImpl::OnMainFrameNavigationCommitted(
@@ -131,11 +124,12 @@
   navigation_committed_time_ = navigation_committed_time;
   main_frame_url_ = url;
   navigation_id_ = navigation_id;
-  SendEvent(resource_coordinator::mojom::Event::kNavigationCommitted);
+  for (auto& observer : observers())
+    observer.OnMainFrameNavigationCommitted(this);
 }
 
-base::flat_set<ProcessNodeImpl*>
-PageNodeImpl::GetAssociatedProcessCoordinationUnits() const {
+base::flat_set<ProcessNodeImpl*> PageNodeImpl::GetAssociatedProcessNodes()
+    const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::flat_set<ProcessNodeImpl*> process_nodes;
   ForAllFrameNodes([&process_nodes](FrameNodeImpl* frame_node) -> bool {
@@ -150,9 +144,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   double cpu_usage = 0;
 
-  for (auto* process_node : GetAssociatedProcessCoordinationUnits()) {
-    size_t pages_in_process =
-        process_node->GetAssociatedPageCoordinationUnits().size();
+  for (auto* process_node : GetAssociatedProcessNodes()) {
+    size_t pages_in_process = process_node->GetAssociatedPageNodes().size();
     DCHECK_LE(1u, pages_in_process);
     cpu_usage += process_node->cpu_usage() / pages_in_process;
   }
@@ -271,23 +264,6 @@
   has_nonempty_beforeunload_ = has_nonempty_beforeunload;
 }
 
-void PageNodeImpl::OnFrameLifecycleStateChanged(
-    FrameNodeImpl* frame_node,
-    resource_coordinator::mojom::LifecycleState old_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(this, frame_node->page_node());
-  DCHECK_NE(old_state, frame_node->lifecycle_state());
-
-  int delta = 0;
-  if (old_state == resource_coordinator::mojom::LifecycleState::kFrozen)
-    delta = -1;
-  else if (frame_node->lifecycle_state() ==
-           resource_coordinator::mojom::LifecycleState::kFrozen)
-    delta = 1;
-  if (delta != 0)
-    OnNumFrozenFramesStateChange(delta);
-}
-
 void PageNodeImpl::OnFrameInterventionPolicyChanged(
     FrameNodeImpl* frame,
     resource_coordinator::mojom::PolicyControlledIntervention intervention,
@@ -352,6 +328,8 @@
 }
 
 bool PageNodeImpl::HasFrame(FrameNodeImpl* frame_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   bool has_node = false;
   ForAllFrameNodes([&has_node, &frame_node](FrameNodeImpl* node) -> bool {
     if (node != frame_node)
@@ -365,51 +343,12 @@
 }
 
 void PageNodeImpl::SetPageAlmostIdle(bool page_almost_idle) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   page_almost_idle_.SetAndMaybeNotify(this, page_almost_idle);
 }
 
-void PageNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
+void PageNodeImpl::SetLifecycleState(LifecycleState lifecycle_state) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (auto& observer : observers())
-    observer.OnPageEventReceived(this, event);
-}
-
-void PageNodeImpl::OnNumFrozenFramesStateChange(int num_frozen_frames_delta) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  num_frozen_frames_ += num_frozen_frames_delta;
-  DCHECK_LE(num_frozen_frames_, frame_node_count_);
-
-  const auto kRunning = resource_coordinator::mojom::LifecycleState::kRunning;
-  const auto kFrozen = resource_coordinator::mojom::LifecycleState::kFrozen;
-
-  // We are interested in knowing when we have transitioned to or from
-  // "fully frozen". A page with no frames is considered to be running by
-  // default.
-  bool was_fully_frozen = lifecycle_state_.value() == kFrozen;
-  bool is_fully_frozen =
-      (frame_node_count_ != 0) && num_frozen_frames_ == frame_node_count_;
-  if (was_fully_frozen == is_fully_frozen)
-    return;
-
-  if (is_fully_frozen) {
-    // Aggregate the beforeunload handler information from the entire frame
-    // tree.
-    bool has_nonempty_beforeunload = false;
-    ForAllFrameNodes(
-        [&has_nonempty_beforeunload](FrameNodeImpl* frame_node) -> bool {
-          if (!frame_node->has_nonempty_beforeunload())
-            return true;
-          has_nonempty_beforeunload = true;
-          return false;
-        });
-
-    set_has_nonempty_beforeunload(has_nonempty_beforeunload);
-  }
-
-  // TODO(fdoray): Store the lifecycle state as a member on the
-  // PageCoordinationUnit rather than as a non-typed property.
-  resource_coordinator::mojom::LifecycleState lifecycle_state =
-      is_fully_frozen ? kFrozen : kRunning;
   lifecycle_state_.SetAndMaybeNotify(this, lifecycle_state);
 }
 
@@ -486,6 +425,7 @@
 
 template <typename MapFunction>
 void PageNodeImpl::ForAllFrameNodes(MapFunction map_function) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto* main_frame_node : main_frame_nodes_)
     ForFrameAndDescendents(main_frame_node, map_function);
 }
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.h b/chrome/browser/performance_manager/graph/page_node_impl.h
index 19f6355..77a5019 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl.h
+++ b/chrome/browser/performance_manager/graph/page_node_impl.h
@@ -52,8 +52,7 @@
   // There is no direct relationship between processes and pages. However,
   // frames are accessible by both processes and frames, so we find all of the
   // processes that are reachable from the pages's accessible frames.
-  base::flat_set<ProcessNodeImpl*> GetAssociatedProcessCoordinationUnits()
-      const;
+  base::flat_set<ProcessNodeImpl*> GetAssociatedProcessNodes() const;
 
   // Returns the average CPU usage that can be attributed to this page over the
   // last measurement period. CPU usage is expressed as the average percentage
@@ -95,11 +94,6 @@
       uint64_t private_footprint_kb_estimate);
   void set_has_nonempty_beforeunload(bool has_nonempty_beforeunload);
 
-  // Invoked when the state of a frame in this page changes.
-  // TODO(chrisha): Move this out to a decorator.
-  void OnFrameLifecycleStateChanged(FrameNodeImpl* frame_node,
-                                    LifecycleState old_state);
-
   // Invoked when a frame belonging to this page changes intervention policy
   // values.
   // TODO(chrisha): Move this out to a decorator.
@@ -134,6 +128,7 @@
 
  private:
   friend class FrameNodeImpl;
+  friend class FrozenFrameAggregatorAccess;
   friend class PageAlmostIdleAccess;
 
   void AddFrame(FrameNodeImpl* frame_node);
@@ -145,16 +140,7 @@
   bool HasFrame(FrameNodeImpl* frame_node);
 
   void SetPageAlmostIdle(bool page_almost_idle);
-
-  // CoordinationUnitInterface implementation.
-  void OnEventReceived(resource_coordinator::mojom::Event event) override;
-
-  // This is called whenever |num_frozen_frames_| changes, or whenever
-  // a frame is added to or removed from this page. It is used to synthesize the
-  // value of |has_nonempty_beforeunload| and to update the LifecycleState of
-  // the page. Calling this with |num_frozen_frames_delta == 0| implies that the
-  // number of frames itself has changed.
-  void OnNumFrozenFramesStateChange(int num_frozen_frames_delta);
+  void SetLifecycleState(LifecycleState lifecycle_state);
 
   // Invalidates all currently aggregated intervention policies.
   void InvalidateAllInterventionPolicies();
@@ -204,9 +190,6 @@
   // The most current memory footprint estimate.
   uint64_t private_footprint_kb_estimate_ = 0;
 
-  // Counts the number of frames in a page that are frozen.
-  size_t num_frozen_frames_ = 0;
-
   // Indicates whether or not this page has a non-empty beforeunload handler.
   // This is an aggregation of the same value on each frame in the page's frame
   // tree. The aggregation is made at the moment all frames associated with a
@@ -263,6 +246,9 @@
   // Storage for PageAlmostIdle user data.
   std::unique_ptr<NodeAttachedData> page_almost_idle_data_;
 
+  // Inline storage for FrozenFrameAggregator user data.
+  InternalNodeAttachedDataStorage<sizeof(uintptr_t) + 8> frozen_frame_data_;
+
   DISALLOW_COPY_AND_ASSIGN(PageNodeImpl);
 };
 
diff --git a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
index 3bbe79e7..43b7f78d 100644
--- a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
@@ -40,7 +40,7 @@
 
 TEST_F(PageNodeImplTest, AddFrameBasic) {
   auto process_node = CreateNode<ProcessNodeImpl>();
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
   auto parent_frame = CreateNode<FrameNodeImpl>(process_node.get(),
                                                 page_node.get(), nullptr, 0);
   auto child1_frame = CreateNode<FrameNodeImpl>(
@@ -54,7 +54,7 @@
 
 TEST_F(PageNodeImplTest, RemoveFrame) {
   auto process_node = CreateNode<ProcessNodeImpl>();
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
   auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(),
                                               page_node.get(), nullptr, 0);
 
@@ -162,31 +162,6 @@
   EXPECT_FALSE(page_node->is_loading());
 }
 
-TEST_F(PageNodeImplTest, OnAllFramesInPageFrozen) {
-  const auto kRunning = resource_coordinator::mojom::LifecycleState::kRunning;
-  const auto kFrozen = resource_coordinator::mojom::LifecycleState::kFrozen;
-
-  MockSinglePageWithMultipleProcessesGraph mock_graph(graph());
-
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-
-  // 1/2 frames in the page is frozen. Expect the page to still be running.
-  mock_graph.frame->SetLifecycleState(kFrozen);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-
-  // 2/2 frames in the process are frozen. We expect the page to be frozen.
-  mock_graph.child_frame->SetLifecycleState(kFrozen);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-
-  // Unfreeze a frame and expect the page to be running again.
-  mock_graph.frame->SetLifecycleState(kRunning);
-  EXPECT_EQ(kRunning, mock_graph.page->lifecycle_state());
-
-  // Refreeze that frame and expect the page to be frozen again.
-  mock_graph.frame->SetLifecycleState(kFrozen);
-  EXPECT_EQ(kFrozen, mock_graph.page->lifecycle_state());
-}
-
 namespace {
 
 const size_t kInterventionCount =
@@ -227,7 +202,7 @@
   TestNodeWrapper<ProcessNodeImpl> process =
       TestNodeWrapper<ProcessNodeImpl>::Create(mock_graph);
   TestNodeWrapper<PageNodeImpl> page =
-      TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr /*TEST*/);
+      TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr);
 
   // Check the initial values before any frames are added.
   EXPECT_EQ(0u, page->GetInterventionPolicyFramesReportedForTesting());
@@ -368,7 +343,7 @@
   TestNodeWrapper<ProcessNodeImpl> process =
       TestNodeWrapper<ProcessNodeImpl>::Create(mock_graph);
   TestNodeWrapper<PageNodeImpl> page =
-      TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr /*TEST*/);
+      TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr);
 
   // Create two frames and immediately attach them to the page.
   TestNodeWrapper<FrameNodeImpl> f0 = TestNodeWrapper<FrameNodeImpl>::Create(
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.cc b/chrome/browser/performance_manager/graph/process_node_impl.cc
index 35803ba..9833e60 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/process_node_impl.cc
@@ -20,12 +20,15 @@
 }
 
 void ProcessNodeImpl::AddFrame(FrameNodeImpl* frame_node) {
-  const bool inserted = frame_nodes_.insert(frame_node).second;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const bool inserted = frame_nodes_.insert(frame_node).second;
   DCHECK(inserted);
-  if (frame_node->lifecycle_state() ==
-      resource_coordinator::mojom::LifecycleState::kFrozen)
-    IncrementNumFrozenFrames();
+}
+
+void ProcessNodeImpl::RemoveFrame(FrameNodeImpl* frame_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(base::ContainsKey(frame_nodes_, frame_node));
+  frame_nodes_.erase(frame_node);
 }
 
 void ProcessNodeImpl::SetCPUUsage(double cpu_usage) {
@@ -67,7 +70,9 @@
 }
 
 void ProcessNodeImpl::OnRendererIsBloated() {
-  SendEvent(resource_coordinator::mojom::Event::kRendererIsBloated);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  for (auto& observer : observers())
+    observer.OnRendererIsBloated(this);
 }
 
 const base::flat_set<FrameNodeImpl*>& ProcessNodeImpl::GetFrameNodes() const {
@@ -79,29 +84,23 @@
 // pages. However, frames are children of both processes and frames, so we
 // find all of the pages that are reachable from the process's child
 // frames.
-base::flat_set<PageNodeImpl*>
-ProcessNodeImpl::GetAssociatedPageCoordinationUnits() const {
+base::flat_set<PageNodeImpl*> ProcessNodeImpl::GetAssociatedPageNodes() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::flat_set<PageNodeImpl*> page_nodes;
-  for (auto* frame_node : frame_nodes_) {
-    if (auto* page_node = frame_node->page_node())
-      page_nodes.insert(page_node);
-  }
+  for (auto* frame_node : frame_nodes_)
+    page_nodes.insert(frame_node->page_node());
   return page_nodes;
 }
 
-void ProcessNodeImpl::OnFrameLifecycleStateChanged(
-    FrameNodeImpl* frame_node,
-    resource_coordinator::mojom::LifecycleState old_state) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(base::ContainsKey(frame_nodes_, frame_node));
-  DCHECK_NE(old_state, frame_node->lifecycle_state());
-
-  if (old_state == resource_coordinator::mojom::LifecycleState::kFrozen)
-    DecrementNumFrozenFrames();
-  else if (frame_node->lifecycle_state() ==
-           resource_coordinator::mojom::LifecycleState::kFrozen)
-    IncrementNumFrozenFrames();
+PageNodeImpl* ProcessNodeImpl::GetPageNodeIfExclusive() const {
+  PageNodeImpl* page_node = nullptr;
+  for (auto* frame_node : frame_nodes_) {
+    if (!page_node)
+      page_node = frame_node->page_node();
+    if (page_node != frame_node->page_node())
+      return nullptr;
+  }
+  return page_node;
 }
 
 void ProcessNodeImpl::SetProcessImpl(base::Process process,
@@ -137,38 +136,4 @@
   DCHECK(frame_nodes_.empty());
 }
 
-void ProcessNodeImpl::OnEventReceived(
-    resource_coordinator::mojom::Event event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (auto& observer : observers())
-    observer.OnProcessEventReceived(this, event);
-}
-
-void ProcessNodeImpl::RemoveFrame(FrameNodeImpl* frame_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(base::ContainsKey(frame_nodes_, frame_node));
-  frame_nodes_.erase(frame_node);
-
-  if (frame_node->lifecycle_state() ==
-      resource_coordinator::mojom::LifecycleState::kFrozen)
-    DecrementNumFrozenFrames();
-}
-
-void ProcessNodeImpl::DecrementNumFrozenFrames() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  --num_frozen_frames_;
-  DCHECK_GE(num_frozen_frames_, 0);
-}
-
-void ProcessNodeImpl::IncrementNumFrozenFrames() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  ++num_frozen_frames_;
-  DCHECK_LE(num_frozen_frames_, static_cast<int>(frame_nodes_.size()));
-
-  if (num_frozen_frames_ == static_cast<int>(frame_nodes_.size())) {
-    for (auto& observer : observers())
-      observer.OnAllFramesInProcessFrozen(this);
-  }
-}
-
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/process_node_impl.h b/chrome/browser/performance_manager/graph/process_node_impl.h
index 4751fdf6..a3bef1b 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl.h
+++ b/chrome/browser/performance_manager/graph/process_node_impl.h
@@ -11,6 +11,7 @@
 #include "base/process/process.h"
 #include "base/process/process_handle.h"
 #include "base/time/time.h"
+#include "chrome/browser/performance_manager/graph/node_attached_data.h"
 #include "chrome/browser/performance_manager/graph/node_base.h"
 #include "chrome/browser/performance_manager/graph/properties.h"
 #include "chrome/browser/performance_manager/observers/graph_observer.h"
@@ -65,7 +66,11 @@
   base::TimeDelta cumulative_cpu_usage() const { return cumulative_cpu_usage_; }
 
   const base::flat_set<FrameNodeImpl*>& GetFrameNodes() const;
-  base::flat_set<PageNodeImpl*> GetAssociatedPageCoordinationUnits() const;
+  base::flat_set<PageNodeImpl*> GetAssociatedPageNodes() const;
+
+  // If this process is associated with only one page, returns that page.
+  // Otherwise, returns nullptr.
+  PageNodeImpl* GetPageNodeIfExclusive() const;
 
   // Use process_id() in preference to process().Pid(). It's always valid to
   // access, but will return kNullProcessId when the process is not valid. It
@@ -91,25 +96,16 @@
   // from the destructor of FrameNodeImpl.
   void RemoveFrame(FrameNodeImpl* frame_node);
 
-  // Invoked when the state of a frame hosted by this process changes.
-  void OnFrameLifecycleStateChanged(
-      FrameNodeImpl* frame_node,
-      resource_coordinator::mojom::LifecycleState old_state);
-
  protected:
   void SetProcessImpl(base::Process process,
                       base::ProcessId process_id,
                       base::Time launch_time);
 
  private:
+  friend class FrozenFrameAggregatorAccess;
+
   void LeaveGraph() override;
 
-  // CoordinationUnitInterface implementation.
-  void OnEventReceived(resource_coordinator::mojom::Event event) override;
-
-  void DecrementNumFrozenFrames();
-  void IncrementNumFrozenFrames();
-
   base::TimeDelta cumulative_cpu_usage_;
   uint64_t private_footprint_kb_ = 0u;
 
@@ -129,8 +125,8 @@
 
   base::flat_set<FrameNodeImpl*> frame_nodes_;
 
-  // The number of frames hosted by this process that are frozen.
-  int num_frozen_frames_ = 0;
+  // Inline storage for FrozenFrameAggregator user data.
+  InternalNodeAttachedDataStorage<sizeof(uintptr_t) + 8> frozen_frame_data_;
 
   DISALLOW_COPY_AND_ASSIGN(ProcessNodeImpl);
 };
diff --git a/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
index d0cc7fb6..8f3575c0 100644
--- a/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/process_node_impl_unittest.cc
@@ -17,22 +17,6 @@
 
 class ProcessNodeImplTest : public GraphTestHarness {};
 
-class MockGraphObserver : public GraphObserver {
- public:
-  MockGraphObserver() = default;
-  virtual ~MockGraphObserver() = default;
-
-  bool ShouldObserve(const NodeBase* node) override {
-    return node->id().type ==
-           resource_coordinator::CoordinationUnitType::kProcess;
-  }
-
-  MOCK_METHOD1(OnAllFramesInProcessFrozen, void(ProcessNodeImpl*));
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockGraphObserver);
-};
-
 }  // namespace
 
 TEST_F(ProcessNodeImplTest, MeasureCPUUsage) {
@@ -41,38 +25,6 @@
   EXPECT_EQ(1.0, process_node->cpu_usage());
 }
 
-TEST_F(ProcessNodeImplTest, OnAllFramesInProcessFrozen) {
-  testing::StrictMock<MockGraphObserver> observer;
-  graph()->RegisterObserver(&observer);
-  MockMultiplePagesInSingleProcessGraph mock_graph(graph());
-
-  // 1/2 frame in the process is frozen.
-  // No call to OnAllFramesInProcessFrozen() is expected.
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-
-  // 2/2 frames in the process are frozen.
-  EXPECT_CALL(observer, OnAllFramesInProcessFrozen(mock_graph.process.get()));
-  mock_graph.other_frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  testing::Mock::VerifyAndClear(&observer);
-
-  // A frame is unfrozen and frozen.
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kRunning);
-  EXPECT_CALL(observer, OnAllFramesInProcessFrozen(mock_graph.process.get()));
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-  testing::Mock::VerifyAndClear(&observer);
-
-  // A frozen frame is frozen again.
-  // No call to OnAllFramesInProcessFrozen() is expected.
-  mock_graph.frame->SetLifecycleState(
-      resource_coordinator::mojom::LifecycleState::kFrozen);
-
-  graph()->UnregisterObserver(&observer);
-}
-
 TEST_F(ProcessNodeImplTest, ProcessLifeCycle) {
   auto process_node = CreateNode<ProcessNodeImpl>();
 
@@ -124,4 +76,28 @@
   EXPECT_EQ(base::TimeDelta(), process_node->cumulative_cpu_usage());
 }
 
+TEST_F(ProcessNodeImplTest, GetPageNodeIfExclusive) {
+  {
+    MockSinglePageInSingleProcessGraph g(graph());
+    EXPECT_EQ(g.page.get(), g.process.get()->GetPageNodeIfExclusive());
+  }
+
+  {
+    MockSinglePageWithMultipleProcessesGraph g(graph());
+    EXPECT_EQ(g.page.get(), g.process.get()->GetPageNodeIfExclusive());
+  }
+
+  {
+    MockMultiplePagesInSingleProcessGraph g(graph());
+    EXPECT_FALSE(g.process.get()->GetPageNodeIfExclusive());
+  }
+
+  {
+    MockMultiplePagesWithMultipleProcessesGraph g(graph());
+    EXPECT_FALSE(g.process.get()->GetPageNodeIfExclusive());
+    EXPECT_EQ(g.other_page.get(),
+              g.other_process.get()->GetPageNodeIfExclusive());
+  }
+}
+
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/system_node_impl.cc b/chrome/browser/performance_manager/graph/system_node_impl.cc
index 997e78e..bef2d26b 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl.cc
+++ b/chrome/browser/performance_manager/graph/system_node_impl.cc
@@ -27,10 +27,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
-void SystemNodeImpl::OnProcessCPUUsageReady() {
-  SendEvent(resource_coordinator::mojom::Event::kProcessCPUUsageReady);
-}
-
 void SystemNodeImpl::DistributeMeasurementBatch(
     std::unique_ptr<ProcessResourceMeasurementBatch> measurement_batch) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -155,14 +151,8 @@
     DCHECK_EQ(last_measurement_end_time_, page->usage_estimate_time());
   }
 
-  // Fire the end update signal.
-  OnProcessCPUUsageReady();
-}
-
-void SystemNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (auto& observer : observers())
-    observer.OnSystemEventReceived(this, event);
+    observer.OnProcessCPUUsageReady(this);
 }
 
 }  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/graph/system_node_impl.h b/chrome/browser/performance_manager/graph/system_node_impl.h
index 7895ea7..27f62a7 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl.h
+++ b/chrome/browser/performance_manager/graph/system_node_impl.h
@@ -54,7 +54,6 @@
   explicit SystemNodeImpl(Graph* graph);
   ~SystemNodeImpl() override;
 
-  void OnProcessCPUUsageReady();
   void DistributeMeasurementBatch(
       std::unique_ptr<ProcessResourceMeasurementBatch> measurement_batch);
 
@@ -71,9 +70,6 @@
   base::TimeTicks last_measurement_start_time_;
   base::TimeTicks last_measurement_end_time_;
 
-  // CoordinationUnitInterface implementation:
-  void OnEventReceived(resource_coordinator::mojom::Event event) override;
-
   DISALLOW_COPY_AND_ASSIGN(SystemNodeImpl);
 };
 
diff --git a/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
index ec3070a1..eb7a977 100644
--- a/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
+++ b/chrome/browser/performance_manager/graph/system_node_impl_unittest.cc
@@ -28,10 +28,7 @@
     return cu_type == resource_coordinator::CoordinationUnitType::kSystem;
   }
 
-  void OnSystemEventReceived(
-      SystemNodeImpl* system_node,
-      resource_coordinator::mojom::Event event) override {
-    EXPECT_EQ(resource_coordinator::mojom::Event::kProcessCPUUsageReady, event);
+  void OnProcessCPUUsageReady(SystemNodeImpl* system_node) override {
     ++system_event_seen_count_;
   }
 
@@ -83,17 +80,6 @@
 
 }  // namespace
 
-TEST_F(SystemNodeImplTest, OnProcessCPUUsageReady) {
-  SystemAndProcessObserver observer;
-  MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
-  mock_graph.system->AddObserver(&observer);
-  EXPECT_EQ(0u, observer.system_event_seen_count());
-  mock_graph.system->OnProcessCPUUsageReady();
-  EXPECT_EQ(1u, observer.system_event_seen_count());
-
-  mock_graph.system->RemoveObserver(&observer);
-}
-
 TEST_F(SystemNodeImplTest, DistributeMeasurementBatch) {
   SystemAndProcessObserver observer;
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
diff --git a/chrome/browser/performance_manager/observers/graph_observer.h b/chrome/browser/performance_manager/observers/graph_observer.h
index 90636130..786e58a 100644
--- a/chrome/browser/performance_manager/observers/graph_observer.h
+++ b/chrome/browser/performance_manager/observers/graph_observer.h
@@ -50,43 +50,32 @@
   // Called when the |node| is about to be destroyed.
   virtual void OnBeforeNodeRemoved(NodeBase* node) {}
 
-  // Called whenever an event is received in |node| if the
-  // |node| doesn't implement its own EventReceived handler.
-  virtual void OnEventReceived(NodeBase* node,
-                               resource_coordinator::mojom::Event event) {}
-  virtual void OnFrameEventReceived(FrameNodeImpl* frame_node,
-                                    resource_coordinator::mojom::Event event) {}
-  virtual void OnPageEventReceived(PageNodeImpl* page_node,
-                                   resource_coordinator::mojom::Event event) {}
-  virtual void OnProcessEventReceived(
-      ProcessNodeImpl* process_node,
-      resource_coordinator::mojom::Event event) {}
-  virtual void OnSystemEventReceived(SystemNodeImpl* system_node,
-                                     resource_coordinator::mojom::Event event) {
-  }
-
   // FrameNodeImpl notifications.
   virtual void OnIsCurrentChanged(FrameNodeImpl* frame_node) {}
   virtual void OnNetworkAlmostIdleChanged(FrameNodeImpl* frame_node) {}
+  virtual void OnLifecycleStateChanged(FrameNodeImpl* frame_node) {}
+  virtual void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) {}
 
   // PageNodeImpl notifications.
   virtual void OnIsVisibleChanged(PageNodeImpl* page_node) {}
   virtual void OnIsLoadingChanged(PageNodeImpl* page_node) {}
   virtual void OnUkmSourceIdChanged(PageNodeImpl* page_node) {}
   virtual void OnLifecycleStateChanged(PageNodeImpl* page_node) {}
+  virtual void OnPageAlmostIdleChanged(PageNodeImpl* page_node) {}
+  virtual void OnFaviconUpdated(PageNodeImpl* page_node) {}
+  virtual void OnTitleUpdated(PageNodeImpl* page_node) {}
+  virtual void OnMainFrameNavigationCommitted(PageNodeImpl* page_node) {}
 
   // ProcessNodeImpl notifications.
   virtual void OnExpectedTaskQueueingDurationSample(
       ProcessNodeImpl* process_node) {}
   virtual void OnMainThreadTaskLoadIsLow(ProcessNodeImpl* process_node) {}
-
-  // Called when page almost idle state changes. This is a computed property and
-  // will only be maintained if a PageAlmostIdleDecorator exists on the graph.
-  virtual void OnPageAlmostIdleChanged(PageNodeImpl* page_node) {}
-
-  // Called when all the frames in a process become frozen.
+  virtual void OnRendererIsBloated(ProcessNodeImpl* process_node) {}
   virtual void OnAllFramesInProcessFrozen(ProcessNodeImpl* process_node) {}
 
+  // SystemNodeImpl notifications.
+  virtual void OnProcessCPUUsageReady(SystemNodeImpl* system_node) {}
+
   void set_node_graph(Graph* graph) { node_graph_ = graph; }
 
   Graph* graph() const { return node_graph_; }
diff --git a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
index 45b177e..fe6706b 100644
--- a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
+++ b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
@@ -53,7 +53,7 @@
 
   {
     auto process_node = CreateNode<ProcessNodeImpl>();
-    auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+    auto page_node = CreateNode<PageNodeImpl>(nullptr);
     auto root_frame_node = CreateNode<FrameNodeImpl>(
         process_node.get(), page_node.get(), nullptr, 0);
     auto frame_node = CreateNode<FrameNodeImpl>(
diff --git a/chrome/browser/performance_manager/observers/metrics_collector.cc b/chrome/browser/performance_manager/observers/metrics_collector.cc
index 05220b4..cfba2e9 100644
--- a/chrome/browser/performance_manager/observers/metrics_collector.cc
+++ b/chrome/browser/performance_manager/observers/metrics_collector.cc
@@ -38,10 +38,8 @@
 // coordination unit.
 size_t GetNumCoresidentTabs(const PageNodeImpl* page_node) {
   std::set<NodeBase*> coresident_tabs;
-  for (auto* process_node :
-       page_node->GetAssociatedProcessCoordinationUnits()) {
-    for (auto* associated_page_node :
-         process_node->GetAssociatedPageCoordinationUnits()) {
+  for (auto* process_node : page_node->GetAssociatedProcessNodes()) {
+    for (auto* associated_page_node : process_node->GetAssociatedPageNodes()) {
       coresident_tabs.insert(associated_page_node);
     }
   }
@@ -76,47 +74,18 @@
   }
 }
 
-void MetricsCollector::OnFrameEventReceived(
-    FrameNodeImpl* frame_node,
-    resource_coordinator::mojom::Event event) {
-  if (event ==
-      resource_coordinator::mojom::Event::kNonPersistentNotificationCreated) {
-    auto* page_node = frame_node->page_node();
-    // Only record metrics while it is backgrounded.
-    if (!page_node || page_node->is_visible() ||
-        !ShouldReportMetrics(page_node)) {
-      return;
-    }
-    MetricsReportRecord& record =
-        metrics_report_record_map_.find(page_node->id())->second;
-    record.first_non_persistent_notification_created.OnSignalReceived(
-        frame_node->IsMainFrame(), page_node->TimeSinceLastVisibilityChange(),
-        graph()->ukm_recorder());
-  }
-}
+void MetricsCollector::OnNonPersistentNotificationCreated(
+    FrameNodeImpl* frame_node) {
+  // Only record metrics while a page is backgrounded.
+  auto* page_node = frame_node->page_node();
+  if (page_node->is_visible() || !ShouldReportMetrics(page_node))
+    return;
 
-void MetricsCollector::OnPageEventReceived(
-    PageNodeImpl* page_node,
-    resource_coordinator::mojom::Event event) {
-  if (event == resource_coordinator::mojom::Event::kTitleUpdated) {
-    // Only record metrics while it is backgrounded.
-    if (page_node->is_visible() || !ShouldReportMetrics(page_node))
-      return;
-    MetricsReportRecord& record =
-        metrics_report_record_map_.find(page_node->id())->second;
-    record.first_title_updated.OnSignalReceived(
-        true, page_node->TimeSinceLastVisibilityChange(),
-        graph()->ukm_recorder());
-  } else if (event == resource_coordinator::mojom::Event::kFaviconUpdated) {
-    // Only record metrics while it is backgrounded.
-    if (page_node->is_visible() || !ShouldReportMetrics(page_node))
-      return;
-    MetricsReportRecord& record =
-        metrics_report_record_map_.find(page_node->id())->second;
-    record.first_favicon_updated.OnSignalReceived(
-        true, page_node->TimeSinceLastVisibilityChange(),
-        graph()->ukm_recorder());
-  }
+  MetricsReportRecord& record =
+      metrics_report_record_map_.find(page_node->id())->second;
+  record.first_non_persistent_notification_created.OnSignalReceived(
+      frame_node->IsMainFrame(), page_node->TimeSinceLastVisibilityChange(),
+      graph()->ukm_recorder());
 }
 
 void MetricsCollector::OnIsVisibleChanged(PageNodeImpl* page_node) {
@@ -135,6 +104,28 @@
   record.UpdateUkmSourceID(ukm_source_id);
 }
 
+void MetricsCollector::OnFaviconUpdated(PageNodeImpl* page_node) {
+  // Only record metrics while it is backgrounded.
+  if (page_node->is_visible() || !ShouldReportMetrics(page_node))
+    return;
+  MetricsReportRecord& record =
+      metrics_report_record_map_.find(page_node->id())->second;
+  record.first_favicon_updated.OnSignalReceived(
+      true, page_node->TimeSinceLastVisibilityChange(),
+      graph()->ukm_recorder());
+}
+
+void MetricsCollector::OnTitleUpdated(PageNodeImpl* page_node) {
+  // Only record metrics while it is backgrounded.
+  if (page_node->is_visible() || !ShouldReportMetrics(page_node))
+    return;
+  MetricsReportRecord& record =
+      metrics_report_record_map_.find(page_node->id())->second;
+  record.first_title_updated.OnSignalReceived(
+      true, page_node->TimeSinceLastVisibilityChange(),
+      graph()->ukm_recorder());
+}
+
 void MetricsCollector::OnExpectedTaskQueueingDurationSample(
     ProcessNodeImpl* process_node) {
   // Report this measurement to all pages that are hosting a main frame in
diff --git a/chrome/browser/performance_manager/observers/metrics_collector.h b/chrome/browser/performance_manager/observers/metrics_collector.h
index d8dd14c..6090e87d3 100644
--- a/chrome/browser/performance_manager/observers/metrics_collector.h
+++ b/chrome/browser/performance_manager/observers/metrics_collector.h
@@ -39,12 +39,11 @@
   bool ShouldObserve(const NodeBase* node) override;
   void OnNodeAdded(NodeBase* node) override;
   void OnBeforeNodeRemoved(NodeBase* node) override;
-  void OnFrameEventReceived(FrameNodeImpl* frame_node,
-                            resource_coordinator::mojom::Event event) override;
-  void OnPageEventReceived(PageNodeImpl* page_node,
-                           resource_coordinator::mojom::Event event) override;
+  void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) override;
   void OnIsVisibleChanged(PageNodeImpl* page_node) override;
   void OnUkmSourceIdChanged(PageNodeImpl* page_node) override;
+  void OnFaviconUpdated(PageNodeImpl* page_node) override;
+  void OnTitleUpdated(PageNodeImpl* page_node) override;
   void OnExpectedTaskQueueingDurationSample(
       ProcessNodeImpl* process_node) override;
 
diff --git a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
index 0290b33..1ad012a 100644
--- a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
+++ b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
@@ -62,7 +62,7 @@
 };
 
 TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstTitleUpdatedUMA) {
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
 
   page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(),
                                             kDummyID, kDummyUrl);
@@ -95,7 +95,7 @@
 
 TEST_F(MAYBE_MetricsCollectorTest,
        FromBackgroundedToFirstTitleUpdatedUMA5MinutesTimeout) {
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
 
   page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(),
                                             kDummyID, kDummyUrl);
@@ -114,7 +114,7 @@
 TEST_F(MAYBE_MetricsCollectorTest,
        FromBackgroundedToFirstNonPersistentNotificationCreatedUMA) {
   auto process_node = CreateNode<ProcessNodeImpl>();
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
   auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(),
                                               page_node.get(), nullptr, 0);
 
@@ -151,7 +151,7 @@
     MAYBE_MetricsCollectorTest,
     FromBackgroundedToFirstNonPersistentNotificationCreatedUMA5MinutesTimeout) {
   auto process_node = CreateNode<ProcessNodeImpl>();
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
   auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(),
                                               page_node.get(), nullptr, 0);
 
@@ -170,7 +170,7 @@
 }
 
 TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstFaviconUpdatedUMA) {
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
 
   page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(),
                                             kDummyID, kDummyUrl);
@@ -203,7 +203,7 @@
 
 TEST_F(MAYBE_MetricsCollectorTest,
        FromBackgroundedToFirstFaviconUpdatedUMA5MinutesTimeout) {
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
 
   page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(),
                                             kDummyID, kDummyUrl);
@@ -222,7 +222,7 @@
 // Flaky test: https://crbug.com/833028
 TEST_F(MAYBE_MetricsCollectorTest, ResponsivenessMetric) {
   auto process_node = CreateNode<ProcessNodeImpl>();
-  auto page_node = CreateNode<PageNodeImpl>(nullptr /*TEST*/);
+  auto page_node = CreateNode<PageNodeImpl>(nullptr);
   auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(),
                                               page_node.get(), nullptr, 0);
 
diff --git a/chrome/browser/performance_manager/observers/page_signal_generator_impl.cc b/chrome/browser/performance_manager/observers/page_signal_generator_impl.cc
index de8a230..e7856db31 100644
--- a/chrome/browser/performance_manager/observers/page_signal_generator_impl.cc
+++ b/chrome/browser/performance_manager/observers/page_signal_generator_impl.cc
@@ -85,13 +85,8 @@
   DCHECK_EQ(1u, count);  // This should always erase exactly one CU.
 }
 
-void PageSignalGeneratorImpl::OnFrameEventReceived(
-    FrameNodeImpl* frame_node,
-    resource_coordinator::mojom::Event event) {
-  if (event !=
-      resource_coordinator::mojom::Event::kNonPersistentNotificationCreated)
-    return;
-
+void PageSignalGeneratorImpl::OnNonPersistentNotificationCreated(
+    FrameNodeImpl* frame_node) {
   auto* page_node = frame_node->page_node();
   if (!page_node)
     return;
@@ -101,57 +96,6 @@
                          NotifyNonPersistentNotificationCreated);
 }
 
-void PageSignalGeneratorImpl::OnProcessEventReceived(
-    ProcessNodeImpl* process_node,
-    resource_coordinator::mojom::Event event) {
-  if (event == resource_coordinator::mojom::Event::kRendererIsBloated) {
-    base::flat_set<PageNodeImpl*> page_nodes =
-        process_node->GetAssociatedPageCoordinationUnits();
-    // Currently bloated renderer handling supports only a single page.
-    if (page_nodes.size() == 1u) {
-      auto* page_node = *page_nodes.begin();
-      DispatchPageSignal(page_node,
-                         &resource_coordinator::mojom::PageSignalReceiver::
-                             NotifyRendererIsBloated);
-      RecordBloatedRendererHandling(
-          BloatedRendererHandlingInResourceCoordinator::kForwardedToBrowser);
-    } else {
-      RecordBloatedRendererHandling(
-          BloatedRendererHandlingInResourceCoordinator::
-              kIgnoredDueToMultiplePages);
-    }
-  }
-}
-
-void PageSignalGeneratorImpl::OnSystemEventReceived(
-    SystemNodeImpl* system_node,
-    resource_coordinator::mojom::Event event) {
-  if (event == resource_coordinator::mojom::Event::kProcessCPUUsageReady) {
-    base::TimeTicks measurement_start =
-        system_node->last_measurement_start_time();
-
-    for (auto& entry : page_data_) {
-      const PageNodeImpl* page = entry.first;
-      PageData* data = &entry.second;
-      // TODO(siggi): Figure "recency" here, to avoid firing a measurement event
-      //     for state transitions that happened "too long" before a
-      //     measurement started. Alternatively perhaps this bit of policy is
-      //     better done in the observer, in which case it needs the time stamps
-      //     involved.
-      if (page->page_almost_idle() && !data->performance_estimate_issued &&
-          data->last_state_change < measurement_start) {
-        DispatchPageSignal(page,
-                           &resource_coordinator::mojom::PageSignalReceiver::
-                               OnLoadTimePerformanceEstimate,
-                           page->TimeSinceLastNavigation(),
-                           page->cumulative_cpu_usage_estimate(),
-                           page->private_footprint_kb_estimate());
-        data->performance_estimate_issued = true;
-      }
-    }
-  }
-}
-
 void PageSignalGeneratorImpl::OnPageAlmostIdleChanged(PageNodeImpl* page_node) {
   GetPageData(page_node)->Reset();
 
@@ -187,6 +131,48 @@
   }
 }
 
+void PageSignalGeneratorImpl::OnRendererIsBloated(
+    ProcessNodeImpl* process_node) {
+  // Currently bloated renderer handling supports only a single page.
+  auto* page_node = process_node->GetPageNodeIfExclusive();
+  if (page_node) {
+    DispatchPageSignal(page_node,
+                       &resource_coordinator::mojom::PageSignalReceiver::
+                           NotifyRendererIsBloated);
+    RecordBloatedRendererHandling(
+        BloatedRendererHandlingInResourceCoordinator::kForwardedToBrowser);
+  } else {
+    RecordBloatedRendererHandling(BloatedRendererHandlingInResourceCoordinator::
+                                      kIgnoredDueToMultiplePages);
+  }
+}
+
+void PageSignalGeneratorImpl::OnProcessCPUUsageReady(
+    SystemNodeImpl* system_node) {
+  base::TimeTicks measurement_start =
+      system_node->last_measurement_start_time();
+
+  for (auto& entry : page_data_) {
+    const PageNodeImpl* page = entry.first;
+    PageData* data = &entry.second;
+    // TODO(siggi): Figure "recency" here, to avoid firing a measurement event
+    //     for state transitions that happened "too long" before a
+    //     measurement started. Alternatively perhaps this bit of policy is
+    //     better done in the observer, in which case it needs the time stamps
+    //     involved.
+    if (page->page_almost_idle() && !data->performance_estimate_issued &&
+        data->last_state_change < measurement_start) {
+      DispatchPageSignal(page,
+                         &resource_coordinator::mojom::PageSignalReceiver::
+                             OnLoadTimePerformanceEstimate,
+                         page->TimeSinceLastNavigation(),
+                         page->cumulative_cpu_usage_estimate(),
+                         page->private_footprint_kb_estimate());
+      data->performance_estimate_issued = true;
+    }
+  }
+}
+
 void PageSignalGeneratorImpl::BindToInterface(
     resource_coordinator::mojom::PageSignalGeneratorRequest request,
     const service_manager::BindSourceInfo& source_info) {
diff --git a/chrome/browser/performance_manager/observers/page_signal_generator_impl.h b/chrome/browser/performance_manager/observers/page_signal_generator_impl.h
index 2b93910..9e5fe4ff 100644
--- a/chrome/browser/performance_manager/observers/page_signal_generator_impl.h
+++ b/chrome/browser/performance_manager/observers/page_signal_generator_impl.h
@@ -23,7 +23,7 @@
 
 // The PageSignalGenerator is a dedicated |GraphObserver| for
 // calculating and emitting page-scoped signals. This observer observes
-// PageCoordinationUnits, ProcessCoordinationUnits and FrameNodes,
+// PageNodes, ProcessNodes and FrameNodes,
 // combining information from the graph to generate page level signals.
 class PageSignalGeneratorImpl
     : public GraphObserver,
@@ -40,17 +40,13 @@
   bool ShouldObserve(const NodeBase* node) override;
   void OnNodeAdded(NodeBase* node) override;
   void OnBeforeNodeRemoved(NodeBase* node) override;
-  void OnFrameEventReceived(FrameNodeImpl* frame_node,
-                            resource_coordinator::mojom::Event event) override;
-  void OnProcessEventReceived(
-      ProcessNodeImpl* page_node,
-      resource_coordinator::mojom::Event event) override;
-  void OnSystemEventReceived(SystemNodeImpl* system_node,
-                             resource_coordinator::mojom::Event event) override;
+  void OnNonPersistentNotificationCreated(FrameNodeImpl* frame_node) override;
   void OnPageAlmostIdleChanged(PageNodeImpl* page_node) override;
   void OnLifecycleStateChanged(PageNodeImpl* page_node) override;
   void OnExpectedTaskQueueingDurationSample(
       ProcessNodeImpl* process_node) override;
+  void OnRendererIsBloated(ProcessNodeImpl* process_node) override;
+  void OnProcessCPUUsageReady(SystemNodeImpl* system_node) override;
 
   void BindToInterface(
       resource_coordinator::mojom::PageSignalGeneratorRequest request,
diff --git a/chrome/browser/performance_manager/observers/page_signal_generator_impl_unittest.cc b/chrome/browser/performance_manager/observers/page_signal_generator_impl_unittest.cc
index 83ddb6c..6a37eea 100644
--- a/chrome/browser/performance_manager/observers/page_signal_generator_impl_unittest.cc
+++ b/chrome/browser/performance_manager/observers/page_signal_generator_impl_unittest.cc
@@ -201,9 +201,7 @@
   // Send a
   // resource_coordinator::mojom::Event::kNonPersistentNotificationCreated event
   // and wait for the receiver to get it.
-  page_signal_generator()->OnFrameEventReceived(
-      frame_node,
-      resource_coordinator::mojom::Event::kNonPersistentNotificationCreated);
+  page_signal_generator()->OnNonPersistentNotificationCreated(frame_node);
   run_loop.Run();
 
   ::testing::Mock::VerifyAndClear(&mock_receiver);
diff --git a/chrome/browser/performance_manager/performance_manager.cc b/chrome/browser/performance_manager/performance_manager.cc
index 593a0eae..70a6ac1 100644
--- a/chrome/browser/performance_manager/performance_manager.cc
+++ b/chrome/browser/performance_manager/performance_manager.cc
@@ -15,6 +15,7 @@
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
 #include "build/build_config.h"
+#include "chrome/browser/performance_manager/decorators/frozen_frame_aggregator.h"
 #include "chrome/browser/performance_manager/decorators/page_almost_idle_decorator.h"
 #include "chrome/browser/performance_manager/graph/page_node_impl.h"
 #include "chrome/browser/performance_manager/graph/system_node_impl.h"
@@ -261,6 +262,7 @@
 
   RegisterObserver(std::make_unique<MetricsCollector>());
   RegisterObserver(std::make_unique<PageAlmostIdleDecorator>());
+  RegisterObserver(std::make_unique<FrozenFrameAggregator>());
 
 #if defined(OS_WIN)
   if (base::FeatureList::IsEnabled(features::kEmptyWorkingSet))
diff --git a/chrome/browser/performance_manager/performance_manager_tab_helper.cc b/chrome/browser/performance_manager/performance_manager_tab_helper.cc
index fd259ef..827b3fd 100644
--- a/chrome/browser/performance_manager/performance_manager_tab_helper.cc
+++ b/chrome/browser/performance_manager/performance_manager_tab_helper.cc
@@ -56,10 +56,9 @@
   std::vector<content::RenderFrameHost*> existing_frames =
       web_contents->GetAllFrames();
   for (content::RenderFrameHost* frame : existing_frames) {
-    // Only send notifications for live frames, the non-live ones will generate
-    // creation notifications when animated.
-    // TODO(siggi): Add an IsRenderFrameCreated accessor to WebContents.
-    if (frame->IsRenderFrameLive())
+    // Only send notifications for created frames, the others will generate
+    // creation notifications in due course (or not at all).
+    if (frame->IsRenderFrameCreated())
       RenderFrameCreated(frame);
   }
 
@@ -134,17 +133,10 @@
 void PerformanceManagerTabHelper::RenderFrameDeleted(
     content::RenderFrameHost* render_frame_host) {
   auto it = frames_.find(render_frame_host);
-  // Apparently there exists a condition where the construction-time iteration
-  // fails to turn up every frame that has been created, and for which there
-  // will be an enventual deletion notification. This is because
-  // IsRenderFrameLive() will return false if the associated process is dead
-  // at the time of query. The process can however be later resurrected, so
-  // the condition can't be tested here.
-  // See https://crbug.com/948088.
-  if (it != frames_.end()) {
-    performance_manager_->DeleteNode(std::move(it->second));
-    frames_.erase(it);
-  }
+  DCHECK(it != frames_.end());
+
+  performance_manager_->DeleteNode(std::move(it->second));
+  frames_.erase(it);
 }
 
 void PerformanceManagerTabHelper::RenderFrameHostChanged(
diff --git a/chrome/browser/performance_manager/performance_manager_unittest.cc b/chrome/browser/performance_manager/performance_manager_unittest.cc
index 1c48c18d..8be3fcb6 100644
--- a/chrome/browser/performance_manager/performance_manager_unittest.cc
+++ b/chrome/browser/performance_manager/performance_manager_unittest.cc
@@ -57,7 +57,7 @@
       performance_manager()->CreateProcessNode();
   EXPECT_NE(nullptr, process_node.get());
   std::unique_ptr<PageNodeImpl> page_node =
-      performance_manager()->CreatePageNode(nullptr /*TEST*/);
+      performance_manager()->CreatePageNode(nullptr);
   EXPECT_NE(nullptr, page_node.get());
 
   // Create a node of each type.
@@ -76,7 +76,7 @@
   std::unique_ptr<ProcessNodeImpl> process_node =
       performance_manager()->CreateProcessNode();
   std::unique_ptr<PageNodeImpl> page_node =
-      performance_manager()->CreatePageNode(nullptr /*TEST*/);
+      performance_manager()->CreatePageNode(nullptr);
 
   std::unique_ptr<FrameNodeImpl> parent1_frame =
       performance_manager()->CreateFrameNode(process_node.get(),
@@ -113,7 +113,7 @@
 TEST_F(PerformanceManagerTest, CallOnGraph) {
   // Create a page node for something to target.
   std::unique_ptr<PageNodeImpl> page_node =
-      performance_manager()->CreatePageNode(nullptr /*TEST*/);
+      performance_manager()->CreatePageNode(nullptr);
 
   PerformanceManager::GraphCallback graph_callback = base::BindLambdaForTesting(
       [&page_node](Graph* graph) { EXPECT_EQ(page_node->graph(), graph); });
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 6028c14..e02aac7 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -224,7 +224,7 @@
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/webui/foreign_session_handler.h"
 #include "chrome/browser/ui/webui/history_ui.h"
-#include "chrome/browser/ui/webui/settings/md_settings_ui.h"
+#include "chrome/browser/ui/webui/settings/settings_ui.h"
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
 #endif  // defined(OS_ANDROID)
 
@@ -266,10 +266,10 @@
 #include "chrome/browser/chromeos/policy/auto_enrollment_client_impl.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/device_wallpaper_image_handler.h"
 #include "chrome/browser/chromeos/policy/dm_token_storage.h"
 #include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h"
 #include "chrome/browser/chromeos/power/power_metrics_reporter.h"
 #include "chrome/browser/chromeos/preferences.h"
@@ -847,7 +847,7 @@
 
 #if !defined(OS_ANDROID)
   HistoryUI::RegisterProfilePrefs(registry);
-  settings::MdSettingsUI::RegisterProfilePrefs(registry);
+  settings::SettingsUI::RegisterProfilePrefs(registry);
 #endif
 
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
diff --git a/chrome/browser/previews/hints_fetcher_browsertest.cc b/chrome/browser/previews/hints_fetcher_browsertest.cc
index 4f531ab..fa3a735 100644
--- a/chrome/browser/previews/hints_fetcher_browsertest.cc
+++ b/chrome/browser/previews/hints_fetcher_browsertest.cc
@@ -15,6 +15,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/engagement/site_engagement_service.h"
 #include "chrome/browser/metrics/subprocess_metrics_provider.h"
 #include "chrome/browser/previews/previews_service.h"
 #include "chrome/browser/previews/previews_service_factory.h"
@@ -111,6 +112,8 @@
     // only provides the URL.
     cmd->AppendSwitchASCII(previews::switches::kOptimizationGuideServiceURL,
                            https_server_->base_url().spec());
+    cmd->AppendSwitchASCII(previews::switches::kFetchHintsOverride,
+                           "example1.com, example2.com");
   }
 
   // Creates hint data for the |hint_setup_url|'s so that OnHintsUpdated in
@@ -138,6 +141,27 @@
     run_loop.Run();
   }
 
+  // Seeds the Site Engagement Service with two HTTP and two HTTPS sites for the
+  // current profile.
+  void SeedSiteEngagementService() {
+    SiteEngagementService* service = SiteEngagementService::Get(
+        Profile::FromBrowserContext(browser()
+                                        ->tab_strip_model()
+                                        ->GetActiveWebContents()
+                                        ->GetBrowserContext()));
+    GURL https_url1("https://images.google.com/");
+    service->AddPointsForTesting(https_url1, 15);
+
+    GURL https_url2("https://news.google.com/");
+    service->AddPointsForTesting(https_url2, 3);
+
+    GURL http_url1("http://photos.google.com/");
+    service->AddPointsForTesting(http_url1, 21);
+
+    GURL http_url2("http://maps.google.com/");
+    service->AddPointsForTesting(http_url2, 2);
+  }
+
   const GURL& https_url() const { return https_url_; }
   const base::HistogramTester* GetHistogramTester() {
     return &histogram_tester_;
@@ -224,3 +248,34 @@
   histogram_tester->ExpectTotalCount(
       "Previews.HintsFetcher.GetHintsRequest.HostCount", 0);
 }
+
+// This test creates a new browser and seeds the Site Engagement Service with
+// both HTTP and HTTPS sites. The test confirms that PreviewsTopHostProviderImpl
+// used by PreviewsOptimizationGuide to provide a list of hosts to HintsFetcher
+// only returns HTTPS-schemed hosts. We verify this with the UMA histogram
+// logged when the GetHintsRequest is made to the remote Optimization Guide
+// Service.
+IN_PROC_BROWSER_TEST_F(OptimizationGuideServiceHintsFetcherBrowserTest,
+                       PreviewsTopHostProviderHTTPSOnly) {
+  const base::HistogramTester* histogram_tester = GetHistogramTester();
+
+  // Adds two HTTP and two HTTPS sites into the Site Engagement Service.
+  SeedSiteEngagementService();
+
+  // This forces the hint cache to be initialized and hints to be fetched.
+  // Whitelist NoScript for https_url()'s' host.
+  SetUpComponentUpdateHints(https_url());
+
+  // Expect that the browser initialization will record at least one sample as
+  // Hints Fetching is enabled. This also ensures that the histograms have been
+  // updated to verify the correct number of hosts that hints will be requested
+  // for.
+  EXPECT_GE(RetryForHistogramUntilCountReached(
+                histogram_tester,
+                "Previews.HintsFetcher.GetHintsRequest.HostCount", 1),
+            1);
+
+  // Only the 2 HTTPS hosts should be requested hints for.
+  histogram_tester->ExpectBucketCount(
+      "Previews.HintsFetcher.GetHintsRequest.HostCount", 2, 1);
+}
diff --git a/chrome/browser/previews/previews_service.cc b/chrome/browser/previews/previews_service.cc
index ef10bf2..2fbf933 100644
--- a/chrome/browser/previews/previews_service.cc
+++ b/chrome/browser/previews/previews_service.cc
@@ -142,7 +142,8 @@
           profile_path.Append(chrome::kPreviewsOptOutDBFilename)),
       optimization_guide_service
           ? std::make_unique<previews::PreviewsOptimizationGuide>(
-                optimization_guide_service, ui_task_runner, profile_path,
+                optimization_guide_service, ui_task_runner,
+                background_task_runner, profile_path,
                 previews_top_host_provider_.get(), previews_url_loader_factory_)
           : nullptr,
       base::Bind(&IsPreviewsTypeEnabled),
diff --git a/chrome/browser/previews/previews_top_host_provider_impl.cc b/chrome/browser/previews/previews_top_host_provider_impl.cc
index 673e7dd7..cbcd9dc1 100644
--- a/chrome/browser/previews/previews_top_host_provider_impl.cc
+++ b/chrome/browser/previews/previews_top_host_provider_impl.cc
@@ -33,15 +33,21 @@
       SiteEngagementService::Get(profile);
 
   // Create a vector of the top hosts by engagement score up to |max_sites|
-  // size. Currently utilizes just the first |max_sites| entries.
-  // TODO(crbug.com/932707): Select TOP HTTPS hosts from site engagement.
+  // size. Currently utilizes just the first |max_sites| entries. Only HTTPS
+  // schemed hosts are included.
+  //
+  // TODO(mcrouse): Filter to only include Top hosts since Data Saver was
+  // enabled.
   std::vector<mojom::SiteEngagementDetails> engagement_details =
       engagement_service->GetAllDetails();
   for (const auto& detail : engagement_details) {
-    if (top_hosts.size() <= max_sites)
-      top_hosts.push_back(detail.origin.host());
-    else
+    if (top_hosts.size() <= max_sites) {
+      if (detail.origin.SchemeIs(url::kHttpsScheme)) {
+        top_hosts.push_back(detail.origin.host());
+      }
+    } else {
       break;
+    }
   }
 
   return top_hosts;
diff --git a/chrome/browser/profiling_host/BUILD.gn b/chrome/browser/profiling_host/BUILD.gn
index 9fd1b0b4..5c73c19 100644
--- a/chrome/browser/profiling_host/BUILD.gn
+++ b/chrome/browser/profiling_host/BUILD.gn
@@ -19,6 +19,7 @@
     "//chrome/common:non_code_constants",
     "//components/heap_profiling",
     "//components/services/heap_profiling/public/cpp",
+    "//components/signin/core/browser",
     "//content/public/browser",
     "//content/public/common",
   ]
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_store.cc b/chrome/browser/resource_coordinator/local_site_characteristics_data_store.cc
index 1003acc..42a6c36 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_store.cc
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_store.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/resource_coordinator/local_site_characteristics_data_writer.h"
 #include "chrome/browser/resource_coordinator/tab_manager_features.h"
 #include "components/history/core/browser/history_service.h"
+#include "components/history/core/browser/url_row.h"
 
 namespace resource_coordinator {
 
@@ -22,6 +23,13 @@
 constexpr char kSiteCharacteristicsDirectoryName[] =
     "Site Characteristics Database";
 
+size_t CountOriginsInURLRows(const history::URLRows& rows) {
+  std::set<url::Origin> origins;
+  for (auto& row : rows)
+    origins.insert(url::Origin::Create(row.url()));
+  return origins.size();
+}
+
 }  // namespace
 
 LocalSiteCharacteristicsDataStore::LocalSiteCharacteristicsDataStore(
@@ -155,17 +163,28 @@
       data.second->ClearObservationsAndInvalidateReadOperation();
     database_->ClearDatabase();
   } else {
-    std::vector<url::Origin> entries_to_remove;
-    for (auto deleted_row : deletion_info.deleted_rows()) {
-      url::Origin origin = url::Origin::Create(deleted_row.url());
-      auto map_iter = origin_data_map_.find(origin);
-      if (map_iter != origin_data_map_.end())
-        map_iter->second->ClearObservationsAndInvalidateReadOperation();
+    std::vector<url::Origin> origins_to_remove;
 
-      // The database will ignore the entries that don't exist in it.
-      entries_to_remove.emplace_back(origin);
+    DCHECK_EQ(deletion_info.deleted_urls_origin_map().size(),
+              CountOriginsInURLRows(deletion_info.deleted_rows()));
+    for (const auto& it : deletion_info.deleted_urls_origin_map()) {
+      const url::Origin origin = url::Origin::Create(it.first);
+      const int remaining_visits_in_history = it.second.first;
+
+      // If the origin no longer exists in history, clear the site
+      // characteristics from memory and from the database.
+      DCHECK_GE(remaining_visits_in_history, 0);
+      if (remaining_visits_in_history == 0) {
+        auto map_iter = origin_data_map_.find(origin);
+        if (map_iter != origin_data_map_.end())
+          map_iter->second->ClearObservationsAndInvalidateReadOperation();
+
+        origins_to_remove.emplace_back(origin);
+      }
     }
-    database_->RemoveSiteCharacteristicsFromDB(entries_to_remove);
+
+    if (!origins_to_remove.empty())
+      database_->RemoveSiteCharacteristicsFromDB(origins_to_remove);
   }
 }
 
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_store.h b/chrome/browser/resource_coordinator/local_site_characteristics_data_store.h
index 9021973a..b4a3ae2 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_store.h
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_store.h
@@ -72,7 +72,11 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(LocalSiteCharacteristicsDataStoreTest, EndToEnd);
   FRIEND_TEST_ALL_PREFIXES(LocalSiteCharacteristicsDataStoreTest,
-                           HistoryServiceObserver);
+                           OnURLsDeleted_Partial_OriginNotReferenced);
+  FRIEND_TEST_ALL_PREFIXES(LocalSiteCharacteristicsDataStoreTest,
+                           OnURLsDeleted_Partial_OriginStillReferenced);
+  FRIEND_TEST_ALL_PREFIXES(LocalSiteCharacteristicsDataStoreTest,
+                           OnURLsDeleted_Full);
 
   // Returns a pointer to the LocalSiteCharacteristicsDataImpl object
   // associated with |origin|, create one and add it to |origin_data_map_|
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_data_store_unittest.cc b/chrome/browser/resource_coordinator/local_site_characteristics_data_store_unittest.cc
index b7903e786..0510fec 100644
--- a/chrome/browser/resource_coordinator/local_site_characteristics_data_store_unittest.cc
+++ b/chrome/browser/resource_coordinator/local_site_characteristics_data_store_unittest.cc
@@ -28,6 +28,8 @@
 const url::Origin kTestOrigin2 =
     url::Origin::Create(GURL("http://www.bar.com"));
 
+constexpr base::TimeDelta kDelay = base::TimeDelta::FromMinutes(1);
+
 class MockLocalSiteCharacteristicsDatabase
     : public testing::NoopLocalSiteCharacteristicsDatabase {
  public:
@@ -45,13 +47,16 @@
 }  // namespace
 
 class LocalSiteCharacteristicsDataStoreTest : public ::testing::Test {
- public:
+ protected:
   LocalSiteCharacteristicsDataStoreTest()
       : scoped_set_tick_clock_for_testing_(&test_clock_) {
     scoped_feature_list_.InitAndEnableFeature(
         features::kSiteCharacteristicsDatabase);
     data_store_ =
         std::make_unique<LocalSiteCharacteristicsDataStore>(&profile_);
+    mock_db_ =
+        new ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase>();
+    data_store_->SetDatabaseForTesting(base::WrapUnique(mock_db_));
     test_clock_.SetNowTicks(base::TimeTicks::UnixEpoch());
     test_clock_.Advance(base::TimeDelta::FromHours(1));
     WaitForAsyncOperationsToComplete();
@@ -63,13 +68,76 @@
     test_browser_thread_bundle_.RunUntilIdle();
   }
 
- protected:
+  // Populates |writer_|, |reader_| and |data_| to refer to a tab navigated to
+  // |kTestOrigin| that updated its title in background. Populates |writer2_|,
+  // |reader2_| and |data2_| to refer to a tab navigated to |kTestOrigin2| that
+  // updates its favicon in background.
+  void SetupTwoSitesUsingFeaturesInBackground() {
+    // Load a first origin, and then make use of a feature on it.
+    ASSERT_FALSE(reader_);
+    reader_ = data_store_->GetReaderForOrigin(kTestOrigin);
+    EXPECT_TRUE(reader_);
+
+    ASSERT_FALSE(writer_);
+    writer_ = data_store_->GetWriterForOrigin(kTestOrigin,
+                                              TabVisibility::kBackground);
+    EXPECT_TRUE(writer_);
+
+    ASSERT_FALSE(data_);
+    data_ =
+        data_store_->origin_data_map_for_testing().find(kTestOrigin)->second;
+    EXPECT_TRUE(data_);
+
+    EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+              reader_->UpdatesTitleInBackground());
+    writer_->NotifySiteLoaded();
+    writer_->NotifyUpdatesTitleInBackground();
+    EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+              reader_->UpdatesTitleInBackground());
+    test_clock_.Advance(kDelay);
+
+    // Load a second origin, make use of a feature on it too.
+    ASSERT_FALSE(reader2_);
+    reader2_ = data_store_->GetReaderForOrigin(kTestOrigin2);
+    EXPECT_TRUE(reader2_);
+
+    ASSERT_FALSE(writer2_);
+    writer2_ = data_store_->GetWriterForOrigin(kTestOrigin2,
+                                               TabVisibility::kBackground);
+    EXPECT_TRUE(writer2_);
+
+    ASSERT_FALSE(data2_);
+    data2_ =
+        data_store_->origin_data_map_for_testing().find(kTestOrigin2)->second;
+    EXPECT_TRUE(data2_);
+
+    EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+              reader2_->UpdatesFaviconInBackground());
+    writer2_->NotifySiteLoaded();
+    writer2_->NotifyUpdatesFaviconInBackground();
+    EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+              reader2_->UpdatesFaviconInBackground());
+    test_clock_.Advance(kDelay);
+  }
+
   base::SimpleTestTickClock test_clock_;
   ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing_;
   content::TestBrowserThreadBundle test_browser_thread_bundle_;
   base::test::ScopedFeatureList scoped_feature_list_;
   TestingProfile profile_;
+
+  // Owned by |data_store_|.
+  ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase>* mock_db_ =
+      nullptr;
   std::unique_ptr<LocalSiteCharacteristicsDataStore> data_store_;
+
+  std::unique_ptr<SiteCharacteristicsDataReader> reader_;
+  std::unique_ptr<SiteCharacteristicsDataWriter> writer_;
+  internal::LocalSiteCharacteristicsDataImpl* data_ = nullptr;
+
+  std::unique_ptr<SiteCharacteristicsDataReader> reader2_;
+  std::unique_ptr<SiteCharacteristicsDataWriter> writer2_;
+  internal::LocalSiteCharacteristicsDataImpl* data2_ = nullptr;
 };
 
 TEST_F(LocalSiteCharacteristicsDataStoreTest, EndToEnd) {
@@ -105,51 +173,19 @@
   writer.reset();
   EXPECT_TRUE(data_store_->origin_data_map_for_testing().empty());
 
+  EXPECT_CALL(*mock_db_, ClearDatabase());
   data_store_->OnURLsDeleted(nullptr, history::DeletionInfo::ForAllHistory());
 }
 
-TEST_F(LocalSiteCharacteristicsDataStoreTest, HistoryServiceObserver) {
-  // Mock the database to ensure that the delete events get propagated properly.
-  ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase>* mock_db =
-      new ::testing::StrictMock<MockLocalSiteCharacteristicsDatabase>();
-  data_store_->SetDatabaseForTesting(base::WrapUnique(mock_db));
+// Verify that an origin is removed from the data store (in memory and on disk)
+// when there are no more references to it in the history, after the history is
+// partially cleared.
+TEST_F(LocalSiteCharacteristicsDataStoreTest,
+       OnURLsDeleted_Partial_OriginNotReferenced) {
+  SetupTwoSitesUsingFeaturesInBackground();
 
-  // Load a first origin, and then make use of a feature on it.
-
-  auto reader = data_store_->GetReaderForOrigin(kTestOrigin);
-  EXPECT_TRUE(reader);
-
-  auto writer =
-      data_store_->GetWriterForOrigin(kTestOrigin, TabVisibility::kBackground);
-  EXPECT_TRUE(writer);
-
-  internal::LocalSiteCharacteristicsDataImpl* data =
-      data_store_->origin_data_map_for_testing().find(kTestOrigin)->second;
-  EXPECT_TRUE(data);
-
-  constexpr base::TimeDelta kDelay = base::TimeDelta::FromHours(1);
-
-  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
-            reader->UpdatesTitleInBackground());
-  writer->NotifySiteLoaded();
-  base::TimeDelta last_loaded_time = data->last_loaded_time_for_testing();
-  writer->NotifyUpdatesTitleInBackground();
-  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
-            reader->UpdatesTitleInBackground());
-  test_clock_.Advance(kDelay);
-
-  // Load a second origin, make use of a feature on it too.
-  auto reader2 = data_store_->GetReaderForOrigin(kTestOrigin2);
-  EXPECT_TRUE(reader2);
-  auto writer2 =
-      data_store_->GetWriterForOrigin(kTestOrigin2, TabVisibility::kBackground);
-  EXPECT_TRUE(writer2);
-  writer2->NotifySiteLoaded();
-  writer2->NotifyUpdatesFaviconInBackground();
-
-  // This site hasn'be been unloaded yet, so the last loaded time shouldn't have
-  // changed.
-  EXPECT_EQ(data->last_loaded_time_for_testing(), last_loaded_time);
+  const base::TimeDelta last_loaded_time2_before_urls_deleted =
+      data2_->last_loaded_time_for_testing();
 
   // Make sure that all data passed to |OnURLsDeleted| get passed to the
   // database, even if they're not in the internal map used by the data store.
@@ -157,45 +193,97 @@
       url::Origin::Create(GURL("http://www.url-not-in-map.com"));
   history::URLRows urls_to_delete = {history::URLRow(kTestOrigin.GetURL()),
                                      history::URLRow(kOriginNotInMap.GetURL())};
-  EXPECT_CALL(*mock_db,
-              RemoveSiteCharacteristicsFromDB(::testing::ContainerEq(
-                  std::vector<url::Origin>({kTestOrigin, kOriginNotInMap}))));
-  data_store_->OnURLsDeleted(nullptr, history::DeletionInfo::ForUrls(
-                                          urls_to_delete, std::set<GURL>()));
-  ::testing::Mock::VerifyAndClear(mock_db);
+  history::DeletionInfo deletion_info =
+      history::DeletionInfo::ForUrls(urls_to_delete, std::set<GURL>());
+  deletion_info.set_deleted_urls_origin_map({
+      {kTestOrigin.GetURL(), {0, base::Time::Now()}},
+      {kOriginNotInMap.GetURL(), {0, base::Time::Now()}},
+  });
+  EXPECT_CALL(*mock_db_,
+              RemoveSiteCharacteristicsFromDB(::testing::WhenSorted(
+                  ::testing::ElementsAre(kTestOrigin, kOriginNotInMap))));
+  data_store_->OnURLsDeleted(nullptr, deletion_info);
+  ::testing::Mock::VerifyAndClear(mock_db_);
 
-  // The information for this site have been reset, so the last loaded time
-  // should now be equal to the current time and the title update feature
-  // observation should have been cleared.
-  EXPECT_NE(data->last_loaded_time_for_testing(), last_loaded_time);
-  EXPECT_EQ(data->last_loaded_time_for_testing(),
+  // The information for the first site should have been cleared. The last
+  // loaded time should be equal to the current time.
+  EXPECT_EQ(data_->last_loaded_time_for_testing(),
             test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
   EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
-            reader->UpdatesTitleInBackground());
+            reader_->UpdatesTitleInBackground());
   // The second site shouldn't have been cleared.
+  EXPECT_EQ(data2_->last_loaded_time_for_testing(),
+            last_loaded_time2_before_urls_deleted);
   EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
-            reader2->UpdatesFaviconInBackground());
+            reader2_->UpdatesFaviconInBackground());
 
-  test_clock_.Advance(kDelay);
+  writer_->NotifySiteUnloaded();
+  writer2_->NotifySiteUnloaded();
+}
+
+// Verify that an origin is *not* removed from the data store (in memory and on
+// disk) when there remain references to it in the history, after the history is
+// partially cleared.
+TEST_F(LocalSiteCharacteristicsDataStoreTest,
+       OnURLsDeleted_Partial_OriginStillReferenced) {
+  SetupTwoSitesUsingFeaturesInBackground();
+
+  const base::TimeDelta last_loaded_time_before_urls_deleted =
+      data_->last_loaded_time_for_testing();
+  const base::TimeDelta last_loaded_time2_before_urls_deleted =
+      data2_->last_loaded_time_for_testing();
+
+  // Make sure that all data passed to |OnURLsDeleted| get passed to the
+  // database, even if they're not in the internal map used by the data store.
+  const url::Origin kOriginNotInMap =
+      url::Origin::Create(GURL("http://www.url-not-in-map.com"));
+  history::URLRows urls_to_delete = {history::URLRow(kTestOrigin.GetURL()),
+                                     history::URLRow(kOriginNotInMap.GetURL())};
+  history::DeletionInfo deletion_info =
+      history::DeletionInfo::ForUrls(urls_to_delete, std::set<GURL>());
+  deletion_info.set_deleted_urls_origin_map({
+      {kTestOrigin.GetURL(), {4, base::Time::Now()}},
+      {kOriginNotInMap.GetURL(), {3, base::Time::Now()}},
+  });
+  data_store_->OnURLsDeleted(nullptr, deletion_info);
+  ::testing::Mock::VerifyAndClear(mock_db_);
+
+  // Sites shouldn't have been cleared.
+  EXPECT_EQ(data_->last_loaded_time_for_testing(),
+            last_loaded_time_before_urls_deleted);
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            reader_->UpdatesTitleInBackground());
+  EXPECT_EQ(data2_->last_loaded_time_for_testing(),
+            last_loaded_time2_before_urls_deleted);
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureInUse,
+            reader2_->UpdatesFaviconInBackground());
+
+  writer_->NotifySiteUnloaded();
+  writer2_->NotifySiteUnloaded();
+}
+
+// Verify that origins are removed from the data store (in memory and on disk)
+// when the history is completely cleared.
+TEST_F(LocalSiteCharacteristicsDataStoreTest, OnURLsDeleted_Full) {
+  SetupTwoSitesUsingFeaturesInBackground();
 
   // Delete all the information stored in the data store.
-  EXPECT_CALL(*mock_db, ClearDatabase());
+  EXPECT_CALL(*mock_db_, ClearDatabase());
   data_store_->OnURLsDeleted(nullptr, history::DeletionInfo::ForAllHistory());
-  ::testing::Mock::VerifyAndClear(mock_db);
+  ::testing::Mock::VerifyAndClear(mock_db_);
 
+  // The information for both sites should have been cleared.
+  EXPECT_EQ(data_->last_loaded_time_for_testing(),
+            test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
   EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
-            reader2->UpdatesFaviconInBackground());
-  EXPECT_EQ(data->last_loaded_time_for_testing(),
+            reader_->UpdatesTitleInBackground());
+  EXPECT_EQ(data2_->last_loaded_time_for_testing(),
             test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
+  EXPECT_EQ(SiteFeatureUsage::kSiteFeatureUsageUnknown,
+            reader2_->UpdatesFaviconInBackground());
 
-  internal::LocalSiteCharacteristicsDataImpl* data2 =
-      data_store_->origin_data_map_for_testing().find(kTestOrigin2)->second;
-  EXPECT_TRUE(data2);
-  EXPECT_EQ(data2->last_loaded_time_for_testing(),
-            test_clock_.NowTicks() - base::TimeTicks::UnixEpoch());
-
-  writer->NotifySiteUnloaded();
-  writer2->NotifySiteUnloaded();
+  writer_->NotifySiteUnloaded();
+  writer2_->NotifySiteUnloaded();
 }
 
 TEST_F(LocalSiteCharacteristicsDataStoreTest, InspectorWorks) {
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.css b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.css
index 42a51c1..677a061 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.css
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.css
@@ -104,10 +104,8 @@
 }
 
 .option .developer-option-icon-button {
-  background-color: #fff;
   float: right;
-  height: 60px;
-  vertical-align: middle;
+  margin-top: 10px;
 }
 
 .option .change-display-style {
@@ -159,4 +157,3 @@
     vertical-align: middle;
     width: 646px;
 }
-
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
index 385f05ee..12c224a8 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/options.html
@@ -12,10 +12,10 @@
 <script type="text/javascript" src="options_loader.js"></script>
 <script type="text/javascript" src="../../chromeVoxChromeOptionsScript.js">
 </script>
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
+<link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
 </head>
 
 <body>
@@ -150,12 +150,12 @@
   <!-- Braille Settings -->
   <div class="chromeos option">
     <label>
-      <paper-icon-button icon="cr:expand-more"
-        class="developer-option-icon-button" id="braille-settings-more"
-        name="chromevox-braille-settings"></paper-icon-button>
-      <paper-icon-button icon="cr:expand-less"
-        class="developer-option-icon-button" id="braille-settings-less"
-        name="chromevox-developer-options" hidden></paper-icon-button>
+      <cr-icon-button iron-icon="cr:expand-more"
+          class="developer-option-icon-button" id="braille-settings-more"
+          name="chromevox-braille-settings"></cr-icon-button>
+      <cr-icon-button iron-icon="cr:expand-less"
+          class="developer-option-icon-button" id="braille-settings-less"
+          name="chromevox-developer-options" hidden></cr-icon-button>
       <span class="i18n" msgid="options_braille_settings">
         Braille Settings
       </span>
@@ -194,12 +194,12 @@
   <!-- Bluetooth braille display -->
   <div class="chromeos option">
     <label>
-      <paper-icon-button icon="cr:expand-more"
-        class="developer-option-icon-button" id="bt-braille-settings-more"
-        name="chromevox-braille-settings"></paper-icon-button>
-      <paper-icon-button icon="cr:expand-less"
-        class="developer-option-icon-button" id="bt-braille-settings-less"
-        name="chromevox-developer-options" hidden></paper-icon-button>
+      <cr-icon-button iron-icon="cr:expand-more"
+          class="developer-option-icon-button" id="bt-braille-settings-more"
+          name="chromevox-braille-settings"></cr-icon-button>
+      <cr-icon-button iron-icon="cr:expand-less"
+          class="developer-option-icon-button" id="bt-braille-settings-less"
+          name="chromevox-developer-options" hidden></cr-icon-button>
       <span class="i18n" msgid="options_bluetooth_braille_display_title">
         Bluetooth braille display
       </span>
@@ -211,12 +211,14 @@
   <!-- Virtual braille display -->
   <div class="chromeos option">
     <label>
-      <paper-icon-button icon="cr:expand-more"
-        class="developer-option-icon-button" id="virtual-braille-settings-more"
-        name="chromevox-braille-settings"></paper-icon-button>
-      <paper-icon-button icon="cr:expand-less"
-        class="developer-option-icon-button" id="virtual-braille-settings-less"
-        name="chromevox-developer-options" hidden></paper-icon-button>
+      <cr-icon-button iron-icon="cr:expand-more"
+          class="developer-option-icon-button"
+          id="virtual-braille-settings-more" name="chromevox-braille-settings">
+      </cr-icon-button>
+      <cr-icon-button iron-icon="cr:expand-less"
+          class="developer-option-icon-button"
+          id="virtual-braille-settings-less" name="chromevox-developer-options"
+          hidden></cr-icon-button>
       <span class="i18n settings-description"
         msgid="options_virtual_braille_display">
         Virtual braille display
@@ -275,16 +277,16 @@
   </h2>
   <div class="chromeos option">
     <label>
-      <paper-icon-button icon="cr:expand-more"
-        aria-describedby="developer-options-label" aria-expanded="false"
-        class="developer-option-icon-button"
-        id="chromevox-developer-options-more"
-        name="chromevox-developer-options"></paper-icon-button>
-      <paper-icon-button icon="cr:expand-less"
-        aria-describedby="developer-options-label" aria-expanded="true"
-        class="developer-option-icon-button"
-        id="chromevox-developer-options-less"
-        name="chromevox-developer-options" hidden></paper-icon-button>
+      <cr-icon-button iron-icon="cr:expand-more"
+          aria-describedby="developer-options-label" aria-expanded="false"
+          class="developer-option-icon-button"
+          id="chromevox-developer-options-more"
+          name="chromevox-developer-options"></cr-icon-button>
+      <cr-icon-button iron-icon="cr:expand-less"
+          aria-describedby="developer-options-label" aria-expanded="true"
+          class="developer-option-icon-button"
+          id="chromevox-developer-options-less"
+          name="chromevox-developer-options" hidden></cr-icon-button>
       <span class="i18n" msgid="options_developer_options"
             id="developer-options-label">
         Enable developer options
@@ -327,10 +329,9 @@
     </div>
      <div class="settings-expand-row" id="show-developer-log">
       <label>
-        <paper-icon-button icon="cr:open-in-new"
-          aria-describedby="show-log-label"
-          class="developer-option-icon-button" id="open-developer-log">
-        </paper-icon-button>
+        <cr-icon-button iron-icon="cr:open-in-new"
+          aria-describedby="show-log-label" class="developer-option-icon-button"
+          id="open-developer-log"></cr-icon-button>
         <span class="i18n settings-description" msgid="options_show_log"
               id="show-log-label">
           Show Log</span>
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index 4a05290..afe04dda 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -49,7 +49,6 @@
     ":saml_interstitial",
     ":sync_consent",
     ":throbber_notice",
-    ":unrecoverable_cryptohome_error_card",
     ":update_required_card",
   ]
 }
@@ -336,8 +335,5 @@
 js_library("throbber_notice") {
 }
 
-js_library("unrecoverable_cryptohome_error_card") {
-}
-
 js_library("update_required_card") {
 }
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html
index 2192ac3..36d9825 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -11,7 +11,6 @@
 <include src="throbber_notice.html">
 <include src="navigation_bar.html">
 <include src="network_select_login.html">
-<include src="unrecoverable_cryptohome_error_card.html">
 <include src="update_required_card.html">
 <include src="offline_ad_login.html">
 <include src="active_directory_password_change.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chrome/browser/resources/chromeos/login/custom_elements_login.js
index eeb4aac..a1ade1a 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -17,7 +17,6 @@
 // <include src="throbber_notice.js">
 // <include src="navigation_bar.js">
 // <include src="network_select_login.js">
-// <include src="unrecoverable_cryptohome_error_card.js">
 // <include src="update_required_card.js">
 // <include src="offline_ad_login.js">
 // <include src="active_directory_password_change.js">
diff --git a/chrome/browser/resources/chromeos/login/gaia_password_changed.html b/chrome/browser/resources/chromeos/login/gaia_password_changed.html
index 30877f3f..974db4a 100644
--- a/chrome/browser/resources/chromeos/login/gaia_password_changed.html
+++ b/chrome/browser/resources/chromeos/login/gaia_password_changed.html
@@ -66,7 +66,8 @@
               <div slot="label" i18n-content="oldPasswordHint"></div>
               <div slot="error" i18n-content="oldPasswordIncorrect"></div>
             </gaia-input>
-            <gaia-button type="link" on-tap="onForgotPasswordClicked_"
+            <gaia-button id="forgot-password-link" type="link"
+                on-tap="onForgotPasswordClicked_"
                 i18n-content="forgotOldPasswordButtonText">
             </gaia-button>
           </gaia-input-form>
@@ -83,7 +84,8 @@
               </p>
             </div>
             <div class="horizontal layout justified center">
-              <gaia-button type="link" on-tap="onTryAgainClicked_"
+              <gaia-button id="try-again-link" type="link"
+                  on-tap="onTryAgainClicked_"
                   i18n-content="passwordChangedTryAgain">
               </gaia-button>
               <gaia-button id="proceedAnywayBtn" on-tap="onProceedClicked_"
diff --git a/chrome/browser/resources/chromeos/login/md_login.html b/chrome/browser/resources/chromeos/login/md_login.html
index a7500833..c57e172 100644
--- a/chrome/browser/resources/chromeos/login/md_login.html
+++ b/chrome/browser/resources/chromeos/login/md_login.html
@@ -70,7 +70,6 @@
 <link rel="stylesheet" href="screen_confirm_password.css">
 <link rel="stylesheet" href="screen_fatal_error.css">
 <link rel="stylesheet" href="screen_device_disabled.css">
-<link rel="stylesheet" href="screen_unrecoverable_cryptohome_error.css">
 <link rel="stylesheet" href="screen_active_directory_password_change.css">
 <link rel="stylesheet" href="screen_update_required.css">
 
diff --git a/chrome/browser/resources/chromeos/login/md_login.js b/chrome/browser/resources/chromeos/login/md_login.js
index 04cfbfc8..de43f1bf 100644
--- a/chrome/browser/resources/chromeos/login/md_login.js
+++ b/chrome/browser/resources/chromeos/login/md_login.js
@@ -38,7 +38,6 @@
 // <include src="screen_confirm_password.js">
 // <include src="screen_fatal_error.js">
 // <include src="screen_device_disabled.js">
-// <include src="screen_unrecoverable_cryptohome_error.js">
 // <include src="screen_active_directory_password_change.js">
 // <include src="screen_encryption_migration.js">
 // <include src="screen_update_required.js">
@@ -92,7 +91,6 @@
       login.ConfirmPasswordScreen.register();
       login.FatalErrorScreen.register();
       login.DeviceDisabledScreen.register();
-      login.UnrecoverableCryptohomeErrorScreen.register();
       login.ActiveDirectoryPasswordChangeScreen.register(/* lazyInit= */ true);
       login.EncryptionMigrationScreen.register();
       login.SupervisionTransitionScreen.register();
diff --git a/chrome/browser/resources/chromeos/login/md_login_screens.html b/chrome/browser/resources/chromeos/login/md_login_screens.html
index e12f206..50b8ee40 100644
--- a/chrome/browser/resources/chromeos/login/md_login_screens.html
+++ b/chrome/browser/resources/chromeos/login/md_login_screens.html
@@ -16,7 +16,6 @@
 <include src="screen_confirm_password.html">
 <include src="screen_fatal_error.html">
 <include src="screen_device_disabled.html">
-<include src="screen_unrecoverable_cryptohome_error.html">
 <include src="screen_active_directory_password_change.html">
 <include src="screen_encryption_migration.html">
 <include src="screen_update_required.html">
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index d6dea95..506f7b5c 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -76,7 +76,6 @@
 <link rel="stylesheet" href="screen_confirm_password.css">
 <link rel="stylesheet" href="screen_fatal_error.css">
 <link rel="stylesheet" href="screen_device_disabled.css">
-<link rel="stylesheet" href="screen_unrecoverable_cryptohome_error.css">
 <link rel="stylesheet" href="screen_active_directory_password_change.css">
 <link rel="stylesheet" href="screen_update_required.css">
 
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index cd3b386..155b9c9 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -39,7 +39,6 @@
 // <include src="screen_confirm_password.js">
 // <include src="screen_fatal_error.js">
 // <include src="screen_device_disabled.js">
-// <include src="screen_unrecoverable_cryptohome_error.js">
 // <include src="screen_active_directory_password_change.js">
 // <include src="screen_encryption_migration.js">
 // <include src="screen_update_required.js">
diff --git a/chrome/browser/resources/chromeos/login/screen_password_changed.js b/chrome/browser/resources/chromeos/login/screen_password_changed.js
index 0e70231..a32c225 100644
--- a/chrome/browser/resources/chromeos/login/screen_password_changed.js
+++ b/chrome/browser/resources/chromeos/login/screen_password_changed.js
@@ -33,7 +33,8 @@
     cancel: function() {
       if (!this.gaiaPasswordChanged_.disabled) {
         chrome.send(
-            'cancelPasswordChangedFlow', [this.gaiaPasswordChanged_.email]);
+            'cancelPasswordChangedFlow',
+            [this.gaiaPasswordChanged_.email || '']);
       }
     },
 
diff --git a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.css b/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.css
deleted file mode 100644
index 83b2bf8b..0000000
--- a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.css
+++ /dev/null
@@ -1,7 +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. */
-
-#unrecoverable-cryptohome-error {
-  width: 448px; /* Should be the same as #gaia-signin. */
-}
diff --git a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.html b/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.html
deleted file mode 100644
index 8ec9891..0000000
--- a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<link rel="import" href="chrome://oobe/custom_elements.html">
-
-<div id="unrecoverable-cryptohome-error" class="step faded"
-    aria-live="polite" hidden>
-  <unrecoverable-cryptohome-error-card id="unrecoverable-cryptohome-error-card">
-  </unrecoverable-cryptohome-error-card>
-  <div id="unrecoverable-cryptohome-error-busy" class="step-loading" hidden>
-    <throbber-notice
-        i18n-values="text:unrecoverableCryptohomeErrorRecreatingProfile">
-    </throbber-notice>
-  </div>
-</div>
diff --git a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js b/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js
deleted file mode 100644
index 2112a3bb..0000000
--- a/chrome/browser/resources/chromeos/login/screen_unrecoverable_cryptohome_error.js
+++ /dev/null
@@ -1,49 +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.
-
-login.createScreen(
-    'UnrecoverableCryptohomeErrorScreen', 'unrecoverable-cryptohome-error',
-    function() {
-      return {
-        EXTERNAL_API: ['show', 'resumeAfterFeedbackUI'],
-
-        /** @override */
-        decorate: function() {
-          this.card_ = $('unrecoverable-cryptohome-error-card');
-          this.throbber_ = $('unrecoverable-cryptohome-error-busy');
-
-          this.card_.addEventListener('done', function(e) {
-            this.setLoading_(true);
-            $('oobe').hidden = true;  // Hide while showing the feedback UI.
-            chrome.send('sendFeedbackAndResyncUserData');
-          }.bind(this));
-        },
-
-        /**
-         * Sets whether to show the loading throbber.
-         * @param {boolean} loading
-         */
-        setLoading_: function(loading) {
-          this.card_.hidden = loading;
-          this.throbber_.hidden = !loading;
-        },
-
-        /**
-         * Show the unrecoverable cryptohome error screen to ask user permission
-         * to collect a feedback report.
-         */
-        show: function() {
-          this.setLoading_(false);
-
-          Oobe.showScreen({id: SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR});
-        },
-
-        /**
-         * Shows the loading UI after the feedback UI is dismissed.
-         */
-        resumeAfterFeedbackUI: function() {
-          $('oobe').hidden = false;
-        }
-      };
-    });
diff --git a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.css b/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.css
deleted file mode 100644
index 2703e0b4..0000000
--- a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.css
+++ /dev/null
@@ -1,18 +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. */
-
-:host .content {
-  padding: 24px 24px 16px;
-}
-
-:host .title {
-  font-weight: bold;
-  white-space: nowrap;
-  word-wrap: normal;
-}
-
-:host .message {
-  margin-bottom: 25px;
-  margin-top: 20px;
-}
diff --git a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html b/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html
deleted file mode 100644
index 5507718..0000000
--- a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.html
+++ /dev/null
@@ -1,34 +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. -->
-
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<!--
-  Unrecoverable cryptohome error card that shows to use when an unrecoverable
-  cryptohome error happens. It asks user to help us debugging the issue by
-  sending us a feedback and allows user to re-create the cryptohome so that
-  the device could still be used.
-
-  Events:
-    'done' - fired when user clicks on continue button.
--->
-<dom-module id="unrecoverable-cryptohome-error-card">
-  <link rel="stylesheet" href="oobe_flex_layout.css">
-  <link rel="stylesheet" href="unrecoverable_cryptohome_error_card.css">
-
-  <template>
-    <div class="content">
-      <div i18n-content="unrecoverableCryptohomeErrorMessageTitle" class="title">
-      </div>
-      <div class="message"
-          i18n-values=".innerHTML:unrecoverableCryptohomeErrorMessage">
-      </div>
-      <div class="horizontal-reverse layout justified center">
-        <gaia-button on-tap="onContinueClicked_"
-            i18n-content="unrecoverableCryptohomeErrorContinue">
-        </gaia-button>
-      </div>
-    </div>
-  </template>
-</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.js b/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.js
deleted file mode 100644
index a92d0c78..0000000
--- a/chrome/browser/resources/chromeos/login/unrecoverable_cryptohome_error_card.js
+++ /dev/null
@@ -1,12 +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.
-
-Polymer({
-  is: 'unrecoverable-cryptohome-error-card',
-
-  /** @private */
-  onContinueClicked_: function() {
-    this.fire('done');
-  },
-});
diff --git a/chrome/browser/resources/chromeos/switch_access/options.js b/chrome/browser/resources/chromeos/switch_access/options.js
index aab4c8e..7b62d789 100644
--- a/chrome/browser/resources/chromeos/switch_access/options.js
+++ b/chrome/browser/resources/chromeos/switch_access/options.js
@@ -7,19 +7,13 @@
  */
 class SwitchAccessOptions {
   constructor() {
-    const background = chrome.extension.getBackgroundPage();
-
     /**
      * SwitchAccess reference.
      * @private {!SwitchAccessInterface}
      */
-    this.switchAccess_ = background.switchAccess;
+    this.switchAccess_ = chrome.extension.getBackgroundPage().switchAccess;
 
     this.init_();
-
-    document.addEventListener('change', this.handleInputChange_.bind(this));
-    background.document.addEventListener(
-        'prefsUpdate', this.handlePrefsUpdate_.bind(this));
   }
 
   /**
@@ -29,6 +23,9 @@
    * @private
    */
   init_() {
+    document.addEventListener('change', this.handleInputChange_.bind(this));
+    chrome.storage.onChanged.addListener(this.handleStorageChange_.bind(this));
+
     document.getElementById('enableAutoScan').checked =
         this.switchAccess_.getBooleanPreference('enableAutoScan');
     document.getElementById('autoScanTime').value =
@@ -106,23 +103,23 @@
   /**
    * Handle a change in user preferences.
    *
-   * @param {!Event} event
+   * @param {!Object} storageChanges
+   * @param {string} areaName
    * @private
    */
-  handlePrefsUpdate_(event) {
-    const updatedPrefs = event.detail;
-    for (let key of Object.keys(updatedPrefs)) {
+  handleStorageChange_(storageChanges, areaName) {
+    for (let key of Object.keys(storageChanges)) {
+      const newValue = storageChanges[key].newValue;
       switch (key) {
         case 'enableAutoScan':
-          document.getElementById(key).checked = updatedPrefs[key];
+          document.getElementById(key).checked = newValue;
           break;
         case 'autoScanTime':
-          document.getElementById(key).value = updatedPrefs[key] / 1000;
+          document.getElementById(key).value = newValue / 1000;
           break;
         default:
           if (this.switchAccess_.getCommands().includes(key))
-            document.getElementById(key).value =
-                String.fromCharCode(updatedPrefs[key]);
+            document.getElementById(key).value = String.fromCharCode(newValue);
       }
     }
   }
diff --git a/chrome/browser/resources/chromeos/switch_access/preferences.js b/chrome/browser/resources/chromeos/switch_access/preferences.js
index 9f64f93..8f6f913 100644
--- a/chrome/browser/resources/chromeos/switch_access/preferences.js
+++ b/chrome/browser/resources/chromeos/switch_access/preferences.js
@@ -27,8 +27,7 @@
 
   /**
    * Store any changes from |chrome.storage.sync| to |this.preferences_|, if
-   * |this.preferences_| is not already set to that value. If
-   * |this.preferences_| changes, fire a |prefsUpdate| event.
+   * |this.preferences_| is not already set to that value.
    *
    * @param {!Object} storageChanges
    * @param {string} areaName
@@ -42,11 +41,8 @@
         updatedPreferences[key] = storageChanges[key].newValue;
       }
     }
-    if (Object.keys(updatedPreferences).length > 0) {
-      let event =
-          new CustomEvent('prefsUpdate', {'detail': updatedPreferences});
-      document.dispatchEvent(event);
-    }
+    if (Object.keys(updatedPreferences).length > 0)
+      this.switchAccess_.onPreferencesChanged(updatedPreferences);
   }
 
   /**
@@ -64,8 +60,7 @@
   /**
    * Asynchronously load the current preferences from |chrome.storage.sync| and
    * store them in |this.preferences_|, if |this.preferences_| is not already
-   * set to that value. If |this.preferences_| changes, fire a |prefsUpdate|
-   * event.
+   * set to that value.
    *
    * @private
    */
@@ -81,11 +76,8 @@
         }
       }
 
-      if (Object.keys(updatedPreferences).length > 0) {
-        const event =
-            new CustomEvent('prefsUpdate', {'detail': updatedPreferences});
-        document.dispatchEvent(event);
-      }
+      if (Object.keys(updatedPreferences).length > 0)
+        this.switchAccess_.onPreferencesChanged(updatedPreferences);
     });
   }
 
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access.js b/chrome/browser/resources/chromeos/switch_access/switch_access.js
index f9bc0f5e..364f3e0 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access.js
@@ -73,9 +73,6 @@
       if (this.navReadyCallback_)
         this.navReadyCallback_();
     }.bind(this));
-
-    document.addEventListener(
-        'prefsUpdate', this.handlePrefsUpdate_.bind(this));
   }
 
   /**
@@ -178,18 +175,16 @@
 
   /**
    * Handle a change in user preferences.
-   * @param {!Event} event
-   * @private
+   * @param {!Object} changes
    */
-  handlePrefsUpdate_(event) {
-    const updatedPrefs = event.detail;
-    for (const key of Object.keys(updatedPrefs)) {
+  onPreferencesChanged(changes) {
+    for (const key of Object.keys(changes)) {
       switch (key) {
         case 'enableAutoScan':
-          this.autoScanManager_.setEnabled(updatedPrefs[key]);
+          this.autoScanManager_.setEnabled(changes[key]);
           break;
         case 'autoScanTime':
-          this.autoScanManager_.setScanTime(updatedPrefs[key]);
+          this.autoScanManager_.setScanTime(changes[key]);
           break;
         default:
           if (this.commands_.getCommands().includes(key))
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
index 2c51db7b..39889fc 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_interface.js
@@ -70,6 +70,12 @@
   performedUserAction() {}
 
   /**
+   * Handle a change in user preferences.
+   * @param {!Object} changes
+   */
+  onPreferencesChanged(changes) {}
+
+  /**
    * Set the value of the preference |key| to |value| in chrome.storage.sync.
    * The behavior is not updated until the storage update is complete.
    *
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log.html b/chrome/browser/resources/extensions/activity_log/activity_log.html
index b896e43..301a0ad 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log.html
+++ b/chrome/browser/resources/extensions/activity_log/activity_log.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/paper_tabs_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
@@ -8,7 +9,6 @@
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-tabs/paper-tabs.html">
 <link rel="import" href="activity_log_history.html">
 <link rel="import" href="activity_log_stream.html">
@@ -36,7 +36,7 @@
       }
 
       paper-tabs {
-        border-bottom: 1px solid var(--google-grey-refresh-300); 
+        border-bottom: 1px solid var(--google-grey-refresh-300);
         font-size: inherit;
         height: 40px;
       }
@@ -63,10 +63,9 @@
     <div class="page-container" id="container">
       <div class="page-content">
         <div class="page-header">
-          <paper-icon-button-light class="icon-arrow-back no-overlap">
-            <button id="closeButton" aria-label="$i18n{back}"
-                on-click="onCloseButtonTap_"></button>
-          </paper-icon-button-light>
+          <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton"
+              aria-label="$i18n{back}" on-click="onCloseButtonTap_">
+          </cr-icon-button>
           <img id="icon" src="[[extensionInfo.iconUrl]]"
               alt$="[[appOrExtension(
                   extensionInfo.type,
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_history.html b/chrome/browser/resources/extensions/activity_log/activity_log_history.html
index 6b6e1b65..f7004f83 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_history.html
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_history.html
@@ -27,7 +27,7 @@
       }
 
       cr-icon-button {
-        margin-inline-start: 0;
+        margin: 0;
       }
 
       .activity-table-headings {
diff --git a/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html b/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html
index 35fb811..5ed47ab 100644
--- a/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html
+++ b/chrome/browser/resources/extensions/activity_log/activity_log_history_item.html
@@ -1,10 +1,10 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="../shared_style.html">
 <link rel="import" href="../shared_vars.html">
 
@@ -14,9 +14,12 @@
       :host {
         border-top: var(--cr-separator-line);
         display: block;
-        /* Unequal padding on left/right side as the paper-icon-button-light's
-         * width is greater than the delete icon's width. */
-        padding: 8px 8px 8px var(--cr-section-padding);
+        /* Unequal padding on left/right side as the cr-icon-button's width is
+         * greater than the delete icon's width. */
+        padding-bottom: 8px;
+        padding-inline-end: 8px;
+        padding-inline-start: var(--cr-section-padding);
+        padding-top: 8px;
       }
 
       #activity-item-main-row {
@@ -93,11 +96,9 @@
           hidden$="[[!isExpandable_]]">
       </cr-expand-button>
       <div class="separator" hidden$="[[!isExpandable_]]"></div>
-      <paper-icon-button-light id="activity-delete" class="icon-delete-gray">
-        <button id="activity-delete-button" aria-describedby="api-call"
-            aria-label="$i18n{clearEntry}" on-click="onDeleteTap_">
-        </button>
-      </paper-icon-button-light>
+      <cr-icon-button id="activity-delete" class="icon-delete-gray"
+          aria-describedby="api-call" aria-label="$i18n{clearEntry}"
+          on-click="onDeleteTap_"></cr-icon-button>
     </div>
     <div id="page-url-list" hidden$="[[!data.expanded]]">
       <template is="dom-repeat" items="[[getPageUrls_(data)]]">
diff --git a/chrome/browser/resources/extensions/detail_view.html b/chrome/browser/resources/extensions/detail_view.html
index 45b3284..b021c518 100644
--- a/chrome/browser/resources/extensions/detail_view.html
+++ b/chrome/browser/resources/extensions/detail_view.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
@@ -16,7 +17,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
 <link rel="import" href="item_behavior.html">
@@ -167,10 +167,9 @@
     <div class="page-container" id="container">
       <div class="page-content">
         <div class="page-header">
-          <paper-icon-button-light class="icon-arrow-back no-overlap">
-            <button id="closeButton" aria-label="$i18n{back}"
-                on-click="onCloseButtonTap_"></button>
-          </paper-icon-button-light>
+          <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton"
+              aria-label="$i18n{back}" on-click="onCloseButtonTap_">
+          </cr-icon-button>
           <img id="icon" src="[[data.iconUrl]]"
               alt$="[[appOrExtension(
                   data.type,
diff --git a/chrome/browser/resources/extensions/error_page.html b/chrome/browser/resources/extensions/error_page.html
index da59eb9..5067418 100644
--- a/chrome/browser/resources/extensions/error_page.html
+++ b/chrome/browser/resources/extensions/error_page.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_container_shadow_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
@@ -12,7 +13,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
 <link rel="import" href="code_section.html">
 <link rel="import" href="item_util.html">
@@ -69,8 +69,8 @@
         padding-inline-start: 0;
       }
 
-      .error-item paper-icon-button-light {
-        margin-inline-end: 0;
+      .error-item cr-icon-button {
+        margin: 0;
       }
 
       .error-item.selected {
@@ -140,11 +140,9 @@
     <div class="page-container" id="container">
       <div class="page-content">
         <div id="heading">
-          <paper-icon-button-light class="icon-arrow-back no-overlap">
-            <button id="closeButton" aria-label="$i18n{back}"
-                on-click="onCloseButtonTap_">
-            </button>
-          </paper-icon-button-light>
+          <cr-icon-button class="icon-arrow-back no-overlap" id="closeButton"
+              aria-label="$i18n{back}" on-click="onCloseButtonTap_">
+          </cr-icon-button>
           <span>$i18n{errorsPageHeading}</span>
           <paper-button on-click="onClearAllTap_" hidden="[[!entries_.length]]">
             $i18n{clearAll}
@@ -170,13 +168,11 @@
                     </div>
                   </div>
                   <div class="separator"></div>
-                  <paper-icon-button-light class="icon-delete-gray">
-                    <button on-click="onDeleteErrorAction_"
-                        aria-describedby$="[[item.id]]"
-                        aria-label="$i18n{clearEntry}"
-                        on-keydown="onDeleteErrorAction_">
-                    </button>
-                  </paper-icon-button-light>
+                  <cr-icon-button class="icon-delete-gray"
+                      on-click="onDeleteErrorAction_"
+                      aria-describedby$="[[item.id]]"
+                      aria-label="$i18n{clearEntry}"
+                      on-keydown="onDeleteErrorAction_"></cr-icon-button>
                 </div>
                 <iron-collapse opened="[[isOpened_(index, selectedEntry_)]]">
                   <div class="devtools-controls">
diff --git a/chrome/browser/resources/extensions/item.html b/chrome/browser/resources/extensions/item.html
index 5cab351..5fa187a 100644
--- a/chrome/browser/resources/extensions/item.html
+++ b/chrome/browser/resources/extensions/item.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
 <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
@@ -19,7 +20,6 @@
 <link rel="import" href="strings.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html">
 <link rel="import" href="navigation_helper.html">
 
@@ -184,7 +184,7 @@
         color: var(--error-color);
       }
 
-      #dev-reload-button-container {
+      #dev-reload-button {
         margin-inline-end: 12px;
       }
 
@@ -321,12 +321,9 @@
           </template>
         </div>
         <template is="dom-if" if="[[!computeDevReloadButtonHidden_(data.*)]]">
-          <paper-icon-button-light id="dev-reload-button-container"
-              class="icon-refresh no-overlap">
-            <button id="dev-reload-button" aria-label="$i18n{itemReload}"
-                aria-describedby="a11yAssociation" on-click="onReloadTap_">
-            </button>
-          </paper-icon-button-light>
+          <cr-icon-button id="dev-reload-button" class="icon-refresh no-overlap"
+              aria-label="$i18n{itemReload}" aria-describedby="a11yAssociation"
+              on-click="onReloadTap_"></cr-icon-button>
         </template>
         <template is="dom-if" if="[[data.disableReasons.corruptInstall]]">
           <paper-button id="repair-button" class="action-button"
diff --git a/chrome/browser/resources/extensions/kiosk_dialog.html b/chrome/browser/resources/extensions/kiosk_dialog.html
index 302e64c..78eb1bae 100644
--- a/chrome/browser/resources/extensions/kiosk_dialog.html
+++ b/chrome/browser/resources/extensions/kiosk_dialog.html
@@ -2,8 +2,9 @@
 
 <link rel="import" href="chrome://resources/cr_elements/cr_checkbox/cr_checkbox.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
 <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_style_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
@@ -11,7 +12,6 @@
 <link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="item_behavior.html">
 <link rel="import" href="kiosk_browser_proxy.html">
 
@@ -64,6 +64,10 @@
       .list-item:hover .item-controls {
         visibility: visible;
       }
+
+      cr-icon-button {
+        margin: 0;
+      }
     </style>
     <cr-dialog id="dialog" close-text="$i18n{close}"
         ignore-enter-key>
@@ -90,9 +94,8 @@
                       '$i18nPolymer{kioskDisableAutoLaunch}',
                       '$i18nPolymer{kioskEnableAutoLaunch}')]]
                 </paper-button>
-                <paper-icon-button-light class="icon-delete-gray">
-                  <button on-click="onDeleteAppTap_"></button>
-                </paper-icon-button-light>
+                <cr-icon-button class="icon-delete-gray"
+                    on-click="onDeleteAppTap_"></cr-icon-button>
               </div>
             </div>
           </template>
diff --git a/chrome/browser/resources/extensions/navigation_helper.html b/chrome/browser/resources/extensions/navigation_helper.html
index 2521c5d5..4ebc7d4 100644
--- a/chrome/browser/resources/extensions/navigation_helper.html
+++ b/chrome/browser/resources/extensions/navigation_helper.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="strings.html">
diff --git a/chrome/browser/resources/extensions/runtime_host_permissions.html b/chrome/browser/resources/extensions/runtime_host_permissions.html
index 9eaad24..32ebf180 100644
--- a/chrome/browser/resources/extensions/runtime_host_permissions.html
+++ b/chrome/browser/resources/extensions/runtime_host_permissions.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
 <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
@@ -63,6 +64,10 @@
       cr-radio-button.multi-row {
         align-items: normal;
       }
+
+      cr-icon-button {
+        margin: 0;
+      }
     </style>
     <div id="permissions-mode">
       <div id="section-heading">
@@ -88,12 +93,9 @@
                   items="[[getRuntimeHosts_(permissions.hosts)]]">
                 <li>
                   <div>[[item]]</div>
-                  <paper-icon-button-light class="icon-more-vert">
-                    <button class="edit-host"
-                        on-click="onEditHostClick_"
-                        title="$i18n{hostPermissionsEdit}">
-                    </button>
-                  </paper-icon-button-light>
+                  <cr-icon-button class="icon-more-vert edit-host"
+                      on-click="onEditHostClick_"
+                      title="$i18n{hostPermissionsEdit}"></cr-icon-button>
                 </li>
               </template>
               <li>
diff --git a/chrome/browser/resources/extensions/shared_style.html b/chrome/browser/resources/extensions/shared_style.html
index 765e6c5..0410a18c 100644
--- a/chrome/browser/resources/extensions/shared_style.html
+++ b/chrome/browser/resources/extensions/shared_style.html
@@ -81,10 +81,6 @@
          * once .separator styling is extracted from settings. */
         margin-inline-start: 0;
       }
-
-      .separator + paper-icon-button-light {
-        margin-inline-start: var(--cr-icon-ripple-margin);
-      }
     </style>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/extensions/shortcut_input.html b/chrome/browser/resources/extensions/shortcut_input.html
index 5d9ce55..e7c6b44 100644
--- a/chrome/browser/resources/extensions/shortcut_input.html
+++ b/chrome/browser/resources/extensions/shortcut_input.html
@@ -1,12 +1,12 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
 <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
 <link rel="import" href="shortcut_util.html">
 
@@ -18,13 +18,13 @@
         width: 200px;
       }
 
-      #clearContainer {
-        --cr-icon-ripple-size: 28px;
+      #clear {
+        --cr-icon-button-size: 28px;
         position: absolute;
         right: 2px;
       }
 
-      :host-context([dir='rtl']) #clearContainer {
+      :host-context([dir='rtl']) #clear {
         left: -2px;
         right: inherit;
       }
@@ -36,11 +36,9 @@
               '$i18nPolymer{shortcutTooManyModifiers}',
               '$i18nPolymer{shortcutNeedCharacter}')]]"
           value="[[computeText_(capturing_, shortcut, pendingShortcut_)]]">
-        <paper-icon-button-light id="clearContainer" slot="suffix"
-            class="icon-cancel no-overlap"
-            hidden$="[[computeClearHidden_(capturing_, shortcut)]]">
-          <button id="clear" on-click="onClearTap_"></button>
-        </paper-icon-button-light>
+        <cr-icon-button id="clear" slot="suffix" class="icon-cancel no-overlap"
+            hidden$="[[computeClearHidden_(capturing_, shortcut)]]"
+            on-click="onClearTap_"></cr-icon-button>
       </cr-input>
     </div>
   </template>
diff --git a/chrome/browser/resources/inline_login/inline_login.css b/chrome/browser/resources/inline_login/inline_login.css
index f550f1c..a749a40f 100644
--- a/chrome/browser/resources/inline_login/inline_login.css
+++ b/chrome/browser/resources/inline_login/inline_login.css
@@ -34,7 +34,8 @@
 }
 
 #navigation-button {
-  color: white;
+  --cr-icon-button-color: white;
+  margin: 0;
   position: absolute;
   top: 0;
   visibility: hidden;
diff --git a/chrome/browser/resources/inline_login/inline_login.html b/chrome/browser/resources/inline_login/inline_login.html
index 74a981386..ab87271 100644
--- a/chrome/browser/resources/inline_login/inline_login.html
+++ b/chrome/browser/resources/inline_login/inline_login.html
@@ -3,8 +3,8 @@
 <head>
   <title>$i18n{title}</title>
   <link rel="import" href="chrome://resources/html/polymer.html">
+  <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
   <link rel="import" href="chrome://resources/cr_elements/icons.html">
-  <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
   <link rel="stylesheet" href="chrome://resources/css/spinner.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" href="chrome://chrome-signin/inline_login.css">
@@ -23,7 +23,6 @@
       <div class="spinner"></div>
     </div>
   </div>
-  <paper-icon-button id="navigation-button"
-                     icon="cr:close"></paper-icon-button>
+  <cr-icon-button id="navigation-button" iron-icon="cr:close"></cr-icon-button>
 </body>
 </html>
diff --git a/chrome/browser/resources/inline_login/inline_login.js b/chrome/browser/resources/inline_login/inline_login.js
index a77f3313..71a6b6b72 100644
--- a/chrome/browser/resources/inline_login/inline_login.js
+++ b/chrome/browser/resources/inline_login/inline_login.js
@@ -134,7 +134,7 @@
   }
 
   function showBackButton() {
-    $('navigation-button').icon =
+    $('navigation-button').ironIcon =
         isRTL() ? 'cr:arrow-forward' : 'cr:arrow-back';
 
     $('navigation-button')
@@ -143,7 +143,7 @@
   }
 
   function showCloseButton() {
-    $('navigation-button').icon = 'cr:close';
+    $('navigation-button').ironIcon = 'cr:close';
     $('navigation-button').classList.add('enabled');
     $('navigation-button')
         .setAttribute(
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/custom_backgrounds.css
index 11a7f0ee..ab8067d 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.css
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.css
@@ -87,6 +87,7 @@
 
 #edit-bg-text {
   display: none;
+  user-select: none;
 }
 
 .ep-enhanced #edit-bg-text {
diff --git a/chrome/browser/resources/pdf/elements/shared-vars.html b/chrome/browser/resources/pdf/elements/shared-vars.html
index 2ba13040..164abe7c 100644
--- a/chrome/browser/resources/pdf/elements/shared-vars.html
+++ b/chrome/browser/resources/pdf/elements/shared-vars.html
@@ -7,12 +7,6 @@
   html {
     --iron-icon-height: 20px;
     --iron-icon-width: 20px;
-    --paper-icon-button: {
-      height: 32px;
-      padding: 6px;
-      width: 32px;
-    };
-    --paper-icon-button-ink-color: rgb(189, 189, 189);
     --viewer-icon-ink-color: rgb(189, 189, 189);
   }
 </style>
diff --git a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html
index 174b498..d3c1167 100644
--- a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html
+++ b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-ripple/paper-ripple.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
@@ -36,14 +37,11 @@
       }
 
       #expand {
-        --iron-icon-height: 16px;
-        --iron-icon-width: 16px;
-        --paper-icon-button-ink-color: var(--paper-grey-900);
-        height: 28px;
-        min-width: 28px;
-        padding: 6px;
+        --cr-icon-button-color: var(--primary-text-color);
+        --cr-icon-button-icon-size: 16px;
+        --cr-icon-button-size: 28px;
+        margin: 0;
         transition: transform 150ms;
-        width: 28px;
       }
 
       :host-context([dir=rtl]) #expand {
@@ -56,9 +54,8 @@
     </style>
     <div id="item" on-click="onClick">
       <paper-ripple></paper-ripple>
-      <paper-icon-button id="expand" icon="cr:chevron-right"
-          on-click="toggleChildren">
-      </paper-icon-button>
+      <cr-icon-button id="expand" iron-icon="cr:chevron-right"
+          on-click="toggleChildren"></cr-icon-button>
       <span id="title" tabindex="0">{{bookmark.title}}</span>
     </div>
     <!-- dom-if will stamp the complex bookmark tree lazily as individual nodes
diff --git a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js
index baad577..160191b6 100644
--- a/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js
+++ b/chrome/browser/resources/pdf/elements/viewer-bookmark/viewer-bookmark.js
@@ -91,7 +91,7 @@
   },
 
   onSpace_: function(e) {
-    // paper-icon-button stops propagation of space events, so there's no need
+    // cr-icon-button stops propagation of space events, so there's no need
     // to check the event source here.
     this.onClick();
     // Prevent default space scroll behavior.
diff --git a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
index 921ce0f2..2f87e5e 100644
--- a/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
+++ b/chrome/browser/resources/pdf/elements/viewer-pdf-toolbar/viewer-pdf-toolbar.html
@@ -1,7 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-progress/paper-progress.html">
 <link rel="import" href="chrome://resources/cr_elements/icons.html">
 <link rel="import" href="../icons.html">
@@ -51,23 +51,16 @@
         user-select: none;
       }
 
-      paper-icon-button {
-        height: 36px;
+      cr-icon-button {
+        --cr-icon-button-color: rgb(241, 241, 241);
         margin: 6px;
-        padding: 8px;
-        width: 36px;
       }
 
-      paper-icon-button:hover {
+      cr-icon-button:hover {
         background: rgba(255, 255, 255, 0.08);
         border-radius: 50%;
       }
 
-      paper-icon-button:focus {
-        --paper-icon-button-ink-color:white;
-        --paper-ripple-opacity: 0.24;
-      }
-
       paper-progress {
         --paper-progress-active-color: var(--google-blue-300);
         --paper-progress-container-color: transparent;
@@ -195,32 +188,24 @@
 
         <div id="buttons" class="invisible">
           <template is="dom-if" if="[[pdfAnnotationsEnabled]]">
-            <paper-icon-button id="annotate" icon="pdf:create"
-                disabled="[[!annotationAvailable]]"
-                on-click="toggleAnnotation"
+            <cr-icon-button id="annotate" iron-icon="pdf:create"
+                disabled="[[!annotationAvailable]]" on-click="toggleAnnotation"
                 aria-label$="{{strings.tooltipAnnotate}}"
-                title$="{{strings.tooltipAnnotate}}">
-            </paper-icon-button>
+                title$="{{strings.tooltipAnnotate}}"></cr-icon-button>
           </template>
 
-          <paper-icon-button id="rotate-right" icon="pdf:rotate-right"
-              disabled="[[annotationMode]]"
-              on-click="rotateRight"
+          <cr-icon-button id="rotate-right" iron-icon="pdf:rotate-right"
+              disabled="[[annotationMode]]" on-click="rotateRight"
               aria-label$="{{strings.tooltipRotateCW}}"
-              title$="{{strings.tooltipRotateCW}}">
-          </paper-icon-button>
+              title$="{{strings.tooltipRotateCW}}"></cr-icon-button>
 
-          <paper-icon-button id="download" icon="cr:file-download"
-              on-click="download"
-              aria-label$="{{strings.tooltipDownload}}"
-              title$="{{strings.tooltipDownload}}">
-          </paper-icon-button>
+          <cr-icon-button id="download" iron-icon="cr:file-download"
+              on-click="download" aria-label$="{{strings.tooltipDownload}}"
+              title$="{{strings.tooltipDownload}}"></cr-icon-button>
 
-          <paper-icon-button id="print" icon="cr:print"
-              on-click="print"
+          <cr-icon-button id="print" iron-icon="cr:print" on-click="print"
               aria-label$="{{strings.tooltipPrint}}"
-              title$="{{strings.tooltipPrint}}">
-          </paper-icon-button>
+              title$="{{strings.tooltipPrint}}"></cr-icon-button>
 
           <viewer-toolbar-dropdown id="bookmarks"
                                    selected
@@ -229,8 +214,8 @@
                                    open-icon="pdf:bookmark"
                                    closed-icon="pdf:bookmark-border"
                                    header="{{strings.bookmarks}}">
-              <viewer-bookmarks-content bookmarks="{{bookmarks}}">
-              </viewer-bookmarks-content>
+            <viewer-bookmarks-content bookmarks="{{bookmarks}}">
+            </viewer-bookmarks-content>
           </viewer-toolbar-dropdown>
         </div>
       </div>
@@ -280,31 +265,23 @@
         </viewer-pen-options>
       </viewer-toolbar-dropdown>
 
-      <paper-icon-button id="eraser"
+      <cr-icon-button id="eraser"
           selected$="[[equal_('eraser', annotationTool.tool)]]"
-          on-click="annotationToolClicked_"
-          icon="pdf:eraser"
+          on-click="annotationToolClicked_" iron-icon="pdf:eraser"
           aria-label$="{{strings.annotationEraser}}"
-          title$="{{strings.annotationEraser}}">
-      </paper-icon-button>
+          title$="{{strings.annotationEraser}}"></cr-icon-button>
 
       <div id="annotation-separator"></div>
 
-      <paper-icon-button id="undo"
-          disabled="[[!canUndoAnnotation]]"
-          icon="pdf:undo"
-          on-click="undo"
+      <cr-icon-button id="undo" disabled="[[!canUndoAnnotation]]"
+          iron-icon="pdf:undo" on-click="undo"
           aria-label$="{{strings.annotationUndo}}"
-          title$="{{strings.annotationUndo}}">
-      </paper-icon-button>
+          title$="{{strings.annotationUndo}}"></cr-icon-button>
 
-      <paper-icon-button id="redo"
-          disabled="[[!canRedoAnnotation]]"
-          icon="pdf:redo"
-          on-click="redo"
+      <cr-icon-button id="redo" disabled="[[!canRedoAnnotation]]"
+          iron-icon="pdf:redo" on-click="redo"
           aria-label$="{{strings.annotationRedo}}"
-          title$="{{strings.annotationRedo}}">
-      </paper-icon-button>
+          title$="{{strings.annotationRedo}}"></cr-icon-button>
     </div>
   </template>
   <script src="viewer-pdf-toolbar.js"></script>
diff --git a/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html b/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html
index 3151736c..dbf18b0 100644
--- a/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html
+++ b/chrome/browser/resources/pdf/elements/viewer-pen-options/viewer-pen-options.html
@@ -1,5 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html">
+
+<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 
 <dom-module id="viewer-pen-options">
   <template>
@@ -14,21 +15,20 @@
     #colors {
       overflow: hidden;
     }
-    input,
     #expand {
-      height: 32px;
-      width: 32px;
-    }
-    #expand {
+      --cr-icon-button-icon-size: 16px;
+      --cr-icon-button-size: 32px;
       grid-column: 8;
       grid-row: 1 / 4;
+      margin: 0;
     }
     input {
       -webkit-appearance: none;
       border-radius: 16px;
+      height: 32px;
       margin: 0;
       padding: 0;
-
+      width: 32px;
     }
     #sizes input {
       background: black;
@@ -70,12 +70,9 @@
             aria-label$="[[lookup_(strings, item.name)]]"
             on-pointerdown="blurOnPointerDown">
       </template>
-      <paper-icon-button id="expand" icon="cr:expand-more"
-          tabindex="3"
-          on-click="toggleExpanded_"
-          aria-label$="[[strings.annotationExpand]]"
-          title$="[[strings.annotationExpand]]">
-      </paper-icon-button>
+      <cr-icon-button id="expand" iron-icon="cr:expand-more" tabindex="3"
+          on-click="toggleExpanded_" aria-label$="[[strings.annotationExpand]]"
+          title$="[[strings.annotationExpand]]"></cr-icon-button>
     </div>
     <div id="separator"></div>
     <div id="sizes" on-change="sizeChanged_">
@@ -88,7 +85,6 @@
             on-pointerdown="blurOnPointerDown">
       </template>
     </div>
-    </paper-icon-button>
   </template>
   <script src="viewer-pen-options.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/print_preview/new/pin_settings.js b/chrome/browser/resources/print_preview/new/pin_settings.js
index fc87cfc0..0aed18d 100644
--- a/chrome/browser/resources/print_preview/new/pin_settings.js
+++ b/chrome/browser/resources/print_preview/new/pin_settings.js
@@ -73,7 +73,7 @@
    * @private
    */
   computeCheckboxDisabled_: function(inputValid, disabled, managed) {
-    return inputValid && (disabled || managed);
+    return managed || (inputValid && disabled);
   },
 
   /**
diff --git a/chrome/browser/resources/print_preview/new/select_behavior.html b/chrome/browser/resources/print_preview/new/select_behavior.html
index f6ef628..2059526 100644
--- a/chrome/browser/resources/print_preview/new/select_behavior.html
+++ b/chrome/browser/resources/print_preview/new/select_behavior.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <link rel="import" href="chrome://resources/html/cr.html">
 
 <script src="select_behavior.js"></script>
diff --git a/chrome/browser/resources/print_preview/print_preview_new.html b/chrome/browser/resources/print_preview/print_preview_new.html
index fa13d77..e2408a5 100644
--- a/chrome/browser/resources/print_preview/print_preview_new.html
+++ b/chrome/browser/resources/print_preview/print_preview_new.html
@@ -1,6 +1,6 @@
 <!doctype html>
 <html dir="$i18n{textdirection}" lang="$i18n{language}" class="loading"
-    $i18n{dark}>
+    $i18n{dark} $i18n{newprintpreviewlayout}>
 <head>
   <meta charset="utf-8">
   <style>
@@ -14,6 +14,14 @@
       background: rgb(189, 193, 198);  /* --google-grey-400 */
     }
 
+    html[new-print-preview-layout] {
+      background: rgb(218, 220, 224);  /* --google-grey-refresh-300 */
+    }
+
+    html[new-print-preview-layout][dark] {
+      background: rgb(95, 99, 104);  /* --google-grey-refresh-700 */
+    }
+
     html,
     body {
       height: 100%;
@@ -22,18 +30,36 @@
       width: 100%;
     }
 
+    .loading body {
+      display: flex;
+    }
+
+    [new-print-preview-layout].loading body {
+      flex-direction: row-reverse;
+    }
+
     .loading body::before {
       background: white;
-      border-inline-end: 1px solid rgb(232, 234, 237);
+      border-bottom-width: 0;
+      border-color: rgb(232, 234, 237);
+      border-inline-end-width: 1px;
+      border-inline-start-width: 0;
+      border-style: solid;
+      border-top-width: 0;
       content: '';
       display: block;
       height: 100%;
       width: 311px;
     }
 
+    [new-print-preview-layout].loading body::before {
+      border-inline-end-width: 0;
+      border-inline-start-width: 1px;
+    }
+
     [dark].loading body::before {
       background: rgb(40, 41, 44);
-      border-inline-end-color: rgba(255, 255, 255, .04);
+      border-color: rgba(255, 255, 255, .04);
     }
   </style>
 </head>
diff --git a/chrome/browser/resources/settings/page_visibility.js b/chrome/browser/resources/settings/page_visibility.js
index 0dee440..c475e67 100644
--- a/chrome/browser/resources/settings/page_visibility.js
+++ b/chrome/browser/resources/settings/page_visibility.js
@@ -66,8 +66,7 @@
    */
   let pageVisibility;
 
-  const showOSSettings = loadTimeData.valueExists('showOSSettings') &&
-      loadTimeData.getBoolean('showOSSettings');
+  const showOSSettings = loadTimeData.getBoolean('showOSSettings');
 
   if (loadTimeData.getBoolean('isGuest')) {
     // "if not chromeos" and "if chromeos" in two completely separate blocks
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 830ccc5..fa4b6f25 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -34,7 +34,7 @@
         <structure name="IDR_SETTINGS_TTS_SUBPAGE_HTML"
                    file="a11y_page/tts_subpage.html"
                    type="chrome_html" />
-        <structure name="IDR_MD_SETTINGS_MANIFEST"
+        <structure name="IDR_SETTINGS_MANIFEST"
                    file="manifest.json"
                    type="chrome_html" />
 
diff --git a/chrome/browser/resources/settings/settings_resources_vulcanized.grd b/chrome/browser/resources/settings/settings_resources_vulcanized.grd
index d6163532..5420b5ed 100644
--- a/chrome/browser/resources/settings/settings_resources_vulcanized.grd
+++ b/chrome/browser/resources/settings/settings_resources_vulcanized.grd
@@ -12,13 +12,13 @@
   </outputs>
   <release seq="1">
     <includes>
-      <include name="IDR_MD_SETTINGS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\vulcanized.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\vulcanized.p2.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\settings\crisper.js" use_base_dir="false" flattenhtml="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_LAZY_LOAD_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.vulcanized.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_LAZY_LOAD_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.vulcanized.p2.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_LAZY_LOAD_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.crisper.js" use_base_dir="false" flattenhtml="true" type="BINDATA" compress="gzip" />
-      <include name="IDR_MD_SETTINGS_MANIFEST" file="manifest.json" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\vulcanized.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\vulcanized.p2.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\settings\crisper.js" use_base_dir="false" flattenhtml="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_LAZY_LOAD_VULCANIZED_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.vulcanized.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_LAZY_LOAD_VULCANIZED_P2_HTML" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.vulcanized.p2.html" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_LAZY_LOAD_CRISPER_JS" file="${root_gen_dir}\chrome\browser\resources\settings\lazy_load.crisper.js" use_base_dir="false" flattenhtml="true" type="BINDATA" compress="gzip" />
+      <include name="IDR_SETTINGS_MANIFEST" file="manifest.json" type="BINDATA" compress="gzip" />
     </includes>
   </release>
 </grit>
diff --git a/chrome/browser/resources/settings/settings_ui/settings_ui.js b/chrome/browser/resources/settings/settings_ui/settings_ui.js
index 35ea54ca..2d14eb3 100644
--- a/chrome/browser/resources/settings/settings_ui/settings_ui.js
+++ b/chrome/browser/resources/settings/settings_ui/settings_ui.js
@@ -143,15 +143,23 @@
     };
     // </if>
 
-    this.showAndroidApps_ = loadTimeData.valueExists('androidAppsVisible') &&
+    // The SplitSettings feature hides OS settings in the browser settings page.
+    // https://crbug.com/950007
+    const showOSSettings = loadTimeData.getBoolean('showOSSettings');
+    this.showAndroidApps_ = showOSSettings &&
+        loadTimeData.valueExists('androidAppsVisible') &&
         loadTimeData.getBoolean('androidAppsVisible');
-    this.showKioskNextShell_ = loadTimeData.valueExists('showKioskNextShell') &&
+    this.showKioskNextShell_ = showOSSettings &&
+        loadTimeData.valueExists('showKioskNextShell') &&
         loadTimeData.getBoolean('showKioskNextShell');
-    this.showCrostini_ = loadTimeData.valueExists('showCrostini') &&
+    this.showCrostini_ = showOSSettings &&
+        loadTimeData.valueExists('showCrostini') &&
         loadTimeData.getBoolean('showCrostini');
-    this.showPluginVm_ = loadTimeData.valueExists('showPluginVm') &&
+    this.showPluginVm_ = showOSSettings &&
+        loadTimeData.valueExists('showPluginVm') &&
         loadTimeData.getBoolean('showPluginVm');
-    this.havePlayStoreApp_ = loadTimeData.valueExists('havePlayStoreApp') &&
+    this.havePlayStoreApp_ = showOSSettings &&
+        loadTimeData.valueExists('havePlayStoreApp') &&
         loadTimeData.getBoolean('havePlayStoreApp');
 
     this.addEventListener('show-container', () => {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/navigation_behavior.html b/chrome/browser/resources/welcome/onboarding_welcome/navigation_behavior.html
index 2bde7e1..1488b4d 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/navigation_behavior.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/navigation_behavior.html
@@ -1,3 +1,5 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <link rel="import" href="chrome://resources/html/assert.html">
 <link rel="import" href="chrome://resources/html/cr.html">
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b9405d4..5b302c4 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1216,10 +1216,6 @@
       "webui/settings/font_handler.h",
       "webui/settings/languages_handler.cc",
       "webui/settings/languages_handler.h",
-      "webui/settings/md_settings_localized_strings_provider.cc",
-      "webui/settings/md_settings_localized_strings_provider.h",
-      "webui/settings/md_settings_ui.cc",
-      "webui/settings/md_settings_ui.h",
       "webui/settings/metrics_reporting_handler.cc",
       "webui/settings/metrics_reporting_handler.h",
       "webui/settings/on_startup_handler.cc",
@@ -1240,6 +1236,8 @@
       "webui/settings/settings_cookies_view_handler.h",
       "webui/settings/settings_import_data_handler.cc",
       "webui/settings/settings_import_data_handler.h",
+      "webui/settings/settings_localized_strings_provider.cc",
+      "webui/settings/settings_localized_strings_provider.h",
       "webui/settings/settings_media_devices_selection_handler.cc",
       "webui/settings/settings_media_devices_selection_handler.h",
       "webui/settings/settings_page_ui_handler.cc",
@@ -1248,6 +1246,8 @@
       "webui/settings/settings_security_key_handler.h",
       "webui/settings/settings_startup_pages_handler.cc",
       "webui/settings/settings_startup_pages_handler.h",
+      "webui/settings/settings_ui.cc",
+      "webui/settings/settings_ui.h",
       "webui/settings/site_settings_handler.cc",
       "webui/settings/site_settings_handler.h",
       "webui/settings_utils.cc",
diff --git a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.h b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.h
index da3f60c..aab52b7 100644
--- a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.h
+++ b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.h
@@ -29,12 +29,6 @@
 // True if the current page is starred. Used by star touch bar button.
 @property(nonatomic, assign) BOOL isStarred;
 
-// True if the back button is enabled.
-@property(nonatomic, assign) BOOL canGoBack;
-
-// True if the forward button is enabled.
-@property(nonatomic, assign) BOOL canGoForward;
-
 // Designated initializer.
 - (instancetype)initWithBrowser:(Browser*)browser
                      controller:(BrowserWindowTouchBarController*)controller;
@@ -44,6 +38,10 @@
 
 - (void)updateWebContents:(content::WebContents*)contents;
 
+// Updates the back/forward button. Called when creating the touch bar or when
+// the back and forward commands have changed.
+- (void)updateBackForwardControl;
+
 - (BrowserWindowTouchBarController*)controller;
 
 @end
@@ -51,11 +49,6 @@
 // Private methods exposed for testing.
 @interface BrowserWindowDefaultTouchBar (ExposedForTesting)
 
-@property(readonly, class) NSString* reloadOrStopItemIdentifier;
-@property(readonly, class) NSString* backItemIdentifier;
-@property(readonly, class) NSString* forwardItemIdentifier;
-@property(readonly, class) NSString* fullscreenOriginItemIdentifier;
-
 // Updates the reload/stop button. Called when creating the touch bar or the
 // page load state has been updated.
 - (void)updateReloadStopButton;
@@ -63,6 +56,10 @@
 // Returns the reload/stop button on the touch bar. Creates it if it's null.
 - (NSButton*)reloadStopButton;
 
+// Returns the back/forward segmented control on the touch bar. Creates it if
+// it's null.
+- (NSSegmentedControl*)backForwardControl;
+
 // Returns the bridge object that BrowserWindowDefaultTouchBar uses to receive
 // notifications.
 - (BookmarkTabHelperObserver*)bookmarkTabObserver;
diff --git a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.mm b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.mm
index d4f5f37..84099212 100644
--- a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.mm
+++ b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar.mm
@@ -51,8 +51,7 @@
 NSString* const kTabFullscreenTouchBarId = @"tab-fullscreen";
 
 // Touch bar items identifiers.
-NSString* const kBackTouchId = @"BACK";
-NSString* const kForwardTouchId = @"FORWARD";
+NSString* const kBackForwardTouchId = @"BACK-FWD";
 NSString* const kReloadOrStopTouchId = @"RELOAD-STOP";
 NSString* const kHomeTouchId = @"HOME";
 NSString* const kSearchTouchId = @"SEARCH";
@@ -60,11 +59,9 @@
 NSString* const kNewTabTouchId = @"NEW-TAB";
 NSString* const kFullscreenOriginLabelTouchId = @"FULLSCREEN-ORIGIN-LABEL";
 
-// This is a combined back and forward control which can no longer be selected
-// but may be in an existing customized Touch Bar. It now represents a group
-// containing the back and forward buttons, and adding the back or forward
-// buttons to the Touch Bar individually magically decomposes the group.
-NSString* const kBackForwardTouchId = @"BACK-FWD";
+// The button indexes in the back and forward segment control.
+const int kBackSegmentIndex = 0;
+const int kForwardSegmentIndex = 1;
 
 // Touch bar icon colors values.
 const SkColor kTouchBarDefaultIconColor = SK_ColorWHITE;
@@ -98,7 +95,7 @@
                          target:owner
                          action:@selector(executeCommand:)];
   button.tag = command;
-  button.accessibilityTitle = l10n_util::GetNSString(tooltip_id);
+  [button setAccessibilityLabel:l10n_util::GetNSString(tooltip_id)];
   return button;
 }
 
@@ -179,10 +176,7 @@
   // CommandObserver:
   void EnabledStateChangedForCommand(int command, bool enabled) override {
     DCHECK(command == IDC_BACK || command == IDC_FORWARD);
-    if (command == IDC_BACK)
-      owner_.canGoBack = enabled;
-    else if (command == IDC_FORWARD)
-      owner_.canGoForward = enabled;
+    [owner_ updateBackForwardControl];
   }
 
   // WebContentsObserver:
@@ -209,6 +203,10 @@
   DISALLOW_COPY_AND_ASSIGN(TouchBarNotificationBridge);
 };
 
+id<NSAccessibility> ToNSAccessibility(id object) {
+  return [object conformsToProtocol:@protocol(NSAccessibility)] ? object : nil;
+}
+
 }  // namespace
 
 @interface BrowserWindowDefaultTouchBar () {
@@ -232,6 +230,9 @@
   // The stop/reload button in the touch bar.
   base::scoped_nsobject<NSButton> reloadStopButton_;
 
+  // The back/forward segmented control in the touch bar.
+  base::scoped_nsobject<NSSegmentedControl> backForwardControl_;
+
   // The starred button in the touch bar.
   base::scoped_nsobject<NSButton> starredButton_;
 }
@@ -239,6 +240,9 @@
 // Creates and returns a touch bar for tab fullscreen mode.
 - (NSTouchBar*)createTabFullscreenTouchBar;
 
+// Sets up the back and forward segmented control.
+- (void)setupBackForwardControl;
+
 // Updates the starred button in the touch bar.
 - (void)updateStarredButton;
 
@@ -251,8 +255,6 @@
 
 @synthesize isPageLoading = isPageLoading_;
 @synthesize isStarred = isStarred_;
-@synthesize canGoBack = canGoBack_;
-@synthesize canGoForward = canGoForward_;
 
 - (instancetype)initWithBrowser:(Browser*)browser
                      controller:(BrowserWindowTouchBarController*)controller {
@@ -264,12 +266,8 @@
     notificationBridge_.reset(new TouchBarNotificationBridge(self, browser));
 
     commandUpdater_ = browser->command_controller();
-
     commandUpdater_->AddCommandObserver(IDC_BACK, notificationBridge_.get());
-    self.canGoBack = commandUpdater_->IsCommandEnabled(IDC_BACK);
-
     commandUpdater_->AddCommandObserver(IDC_FORWARD, notificationBridge_.get());
-    self.canGoForward = commandUpdater_->IsCommandEnabled(IDC_FORWARD);
 
     PrefService* prefs = browser->profile()->GetPrefs();
     showHomeButton_.Init(
@@ -303,12 +301,12 @@
       setCustomizationIdentifier:ui::GetTouchBarId(kBrowserWindowTouchBarId)];
   [touchBar setDelegate:self];
 
-  NSMutableArray<NSString*>* customIdentifiers = [NSMutableArray array];
-  NSMutableArray<NSString*>* defaultIdentifiers = [NSMutableArray array];
+  NSMutableArray* customIdentifiers = [NSMutableArray arrayWithCapacity:7];
+  NSMutableArray* defaultIdentifiers = [NSMutableArray arrayWithCapacity:6];
 
-  NSArray<NSString*>* touchBarItems = @[
-    kBackTouchId, kForwardTouchId, kReloadOrStopTouchId, kHomeTouchId,
-    kSearchTouchId, kStarTouchId, kNewTabTouchId
+  NSArray* touchBarItems = @[
+    kBackForwardTouchId, kReloadOrStopTouchId, kHomeTouchId, kSearchTouchId,
+    kStarTouchId, kNewTabTouchId
   ];
 
   for (NSString* item in touchBarItems) {
@@ -334,41 +332,14 @@
   if (!touchBar)
     return nil;
 
-  if ([identifier hasSuffix:kBackForwardTouchId]) {
-    auto* items = @[
-      [touchBar itemForIdentifier:ui::GetTouchBarItemId(
-                                      kBrowserWindowTouchBarId, kBackTouchId)],
-      [touchBar
-          itemForIdentifier:ui::GetTouchBarItemId(kBrowserWindowTouchBarId,
-                                                  kForwardTouchId)],
-    ];
-    auto groupItem = [NSGroupTouchBarItem groupItemWithIdentifier:identifier
-                                                            items:items];
-    [groupItem setCustomizationLabel:
-                   l10n_util::GetNSString(
-                       IDS_TOUCH_BAR_BACK_FORWARD_CUSTOMIZATION_LABEL)];
-    return groupItem;
-  }
-
   base::scoped_nsobject<NSCustomTouchBarItem> touchBarItem(
       [[ui::NSCustomTouchBarItem() alloc] initWithIdentifier:identifier]);
-  if ([identifier hasSuffix:kBackTouchId]) {
-    auto* button = CreateTouchBarButton(vector_icons::kBackArrowIcon, self,
-                                        IDC_BACK, IDS_ACCNAME_BACK);
-    [button bind:@"enabled" toObject:self withKeyPath:@"canGoBack" options:nil];
-    [touchBarItem setView:button];
-    [touchBarItem
-        setCustomizationLabel:l10n_util::GetNSString(IDS_ACCNAME_BACK)];
-  } else if ([identifier hasSuffix:kForwardTouchId]) {
-    auto* button = CreateTouchBarButton(vector_icons::kForwardArrowIcon, self,
-                                        IDC_FORWARD, IDS_ACCNAME_FORWARD);
-    [button bind:@"enabled"
-           toObject:self
-        withKeyPath:@"canGoForward"
-            options:nil];
-    [touchBarItem setView:button];
-    [touchBarItem
-        setCustomizationLabel:l10n_util::GetNSString(IDS_ACCNAME_FORWARD)];
+  if ([identifier hasSuffix:kBackForwardTouchId]) {
+    [self updateBackForwardControl];
+    [touchBarItem setView:backForwardControl_.get()];
+    [touchBarItem setCustomizationLabel:
+                      l10n_util::GetNSString(
+                          IDS_TOUCH_BAR_BACK_FORWARD_CUSTOMIZATION_LABEL)];
   } else if ([identifier hasSuffix:kReloadOrStopTouchId]) {
     [self updateReloadStopButton];
     [touchBarItem setView:reloadStopButton_.get()];
@@ -427,8 +398,6 @@
 
     [touchBarItem
         setView:[NSTextField labelWithAttributedString:attributedString.get()]];
-  } else {
-    return nil;
   }
 
   return touchBarItem.autorelease();
@@ -443,10 +412,63 @@
   return touchBar.autorelease();
 }
 
+- (void)setupBackForwardControl {
+  NSMutableArray* images = [NSMutableArray arrayWithArray:@[
+    CreateNSImageFromIcon(vector_icons::kBackArrowIcon),
+    CreateNSImageFromIcon(vector_icons::kForwardArrowIcon)
+  ]];
+
+  // Offset the icons so that it matches the height of the other Touch Bar
+  // items.
+  const int kIconYOffset = 2;
+  for (NSUInteger i = 0; i < [images count]; i++) {
+    NSImage* image = [images objectAtIndex:i];
+    NSSize size = [image size];
+    size.height += kIconYOffset;
+
+    NSImage* offsettedImage = [[[NSImage alloc] initWithSize:size] autorelease];
+    [offsettedImage lockFocus];
+    [image drawInRect:NSMakeRect(0, 0, size.width, size.height - kIconYOffset)];
+    [offsettedImage unlockFocus];
+    [images replaceObjectAtIndex:i withObject:offsettedImage];
+  }
+
+  NSSegmentedControl* control = [NSSegmentedControl
+      segmentedControlWithImages:images
+                    trackingMode:NSSegmentSwitchTrackingMomentary
+                          target:self
+                          action:@selector(backOrForward:)];
+
+  // Use the accessibility protocol to get the children.
+  // Use NSAccessibilityUnignoredDescendant to be sure we start with
+  // the correct object.
+  id<NSAccessibility> segmentElement =
+      ToNSAccessibility(NSAccessibilityUnignoredDescendant(control));
+  DCHECK(segmentElement);
+  NSArray<id<NSAccessibility>>* segments = segmentElement.accessibilityChildren;
+  ToNSAccessibility(segments[0]).accessibilityTitle =
+      l10n_util::GetNSString(IDS_ACCNAME_BACK);
+  ToNSAccessibility(segments[1]).accessibilityTitle =
+      l10n_util::GetNSString(IDS_ACCNAME_FORWARD);
+
+  backForwardControl_.reset([control retain]);
+}
+
 - (void)updateWebContents:(content::WebContents*)contents {
   notificationBridge_->UpdateWebContents(contents);
 }
 
+- (void)updateBackForwardControl {
+  if (!backForwardControl_)
+    [self setupBackForwardControl];
+
+  [backForwardControl_ setSegmentStyle:NSSegmentStyleSeparated];
+  [backForwardControl_ setEnabled:commandUpdater_->IsCommandEnabled(IDC_BACK)
+                       forSegment:kBackSegmentIndex];
+  [backForwardControl_ setEnabled:commandUpdater_->IsCommandEnabled(IDC_FORWARD)
+                       forSegment:kForwardSegmentIndex];
+}
+
 - (void)updateStarredButton {
   const gfx::VectorIcon& icon =
       isStarred_ ? omnibox::kStarActiveIcon : omnibox::kStarIcon;
@@ -510,6 +532,14 @@
   return searchButton;
 }
 
+- (void)backOrForward:(id)sender {
+  NSSegmentedControl* control = sender;
+  int command =
+      [control selectedSegment] == kBackSegmentIndex ? IDC_BACK : IDC_FORWARD;
+  LogTouchBarUMA(TouchBarActionFromCommand(command));
+  commandUpdater_->ExecuteCommand(command);
+}
+
 - (void)executeCommand:(id)sender {
   int command = [sender tag];
   ui::LogTouchBarUMA(TouchBarActionFromCommand(command));
@@ -531,23 +561,6 @@
 // Private methods exposed for testing.
 @implementation BrowserWindowDefaultTouchBar (ExposedForTesting)
 
-+ (NSString*)reloadOrStopItemIdentifier {
-  return ui::GetTouchBarItemId(kBrowserWindowTouchBarId, kReloadOrStopTouchId);
-}
-
-+ (NSString*)backItemIdentifier {
-  return ui::GetTouchBarItemId(kBrowserWindowTouchBarId, kBackTouchId);
-}
-
-+ (NSString*)forwardItemIdentifier {
-  return ui::GetTouchBarItemId(kBrowserWindowTouchBarId, kForwardTouchId);
-}
-
-+ (NSString*)fullscreenOriginItemIdentifier {
-  return ui::GetTouchBarItemId(kTabFullscreenTouchBarId,
-                               kFullscreenOriginLabelTouchId);
-}
-
 - (void)updateReloadStopButton {
   const gfx::VectorIcon& icon =
       isPageLoading_ ? kNavigateStopIcon : vector_icons::kReloadIcon;
@@ -573,6 +586,13 @@
   return reloadStopButton_.get();
 }
 
+- (NSSegmentedControl*)backForwardControl {
+  if (!backForwardControl_)
+    [self updateBackForwardControl];
+
+  return backForwardControl_.get();
+}
+
 - (BookmarkTabHelperObserver*)bookmarkTabObserver {
   return notificationBridge_.get();
 }
diff --git a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm
index 7f679ff..ec36da0d 100644
--- a/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm
+++ b/chrome/browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm
@@ -18,14 +18,33 @@
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
-#include "content/public/test/test_renderer_host.h"
-#include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/gtest_mac.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
 #import "ui/base/cocoa/touch_bar_util.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 
+namespace {
+
+// Touch bar identifiers.
+NSString* const kBrowserWindowTouchBarId = @"browser-window";
+NSString* const kTabFullscreenTouchBarId = @"tab-fullscreen";
+
+// Touch bar items identifiers.
+NSString* const kBackForwardTouchId = @"BACK-FWD";
+NSString* const kReloadOrStopTouchId = @"RELOAD-STOP";
+NSString* const kHomeTouchId = @"HOME";
+NSString* const kSearchTouchId = @"SEARCH";
+NSString* const kStarTouchId = @"BOOKMARK";
+NSString* const kNewTabTouchId = @"NEW-TAB";
+NSString* const kFullscreenOriginLabelTouchId = @"FULLSCREEN-ORIGIN-LABEL";
+
+// The button indexes in the back and forward segment control.
+const int kBackSegmentIndex = 0;
+const int kForwardSegmentIndex = 1;
+
+}  // namespace
+
 class BrowserWindowDefaultTouchBarUnitTest : public CocoaProfileTest {
  public:
   void SetUp() override {
@@ -34,10 +53,6 @@
 
     command_updater_ = browser()->command_controller();
 
-    browser()->tab_strip_model()->AppendWebContents(
-        content::WebContentsTester::CreateTestWebContents(profile(), nullptr),
-        true);
-
     if (@available(macOS 10.12.2, *)) {
       touch_bar_.reset([[BrowserWindowDefaultTouchBar alloc]
           initWithBrowser:browser()
@@ -45,107 +60,68 @@
     }
   }
 
+  NSString* GetFullscreenTouchBarItemId(NSString* id) {
+    return ui::GetTouchBarItemId(kTabFullscreenTouchBarId, id);
+  }
+
+  NSString* GetBrowserTouchBarItemId(NSString* id) {
+    return ui::GetTouchBarItemId(kBrowserWindowTouchBarId, id);
+  }
+
   void UpdateCommandEnabled(int id, bool enabled) {
     command_updater_->UpdateCommandEnabled(id, enabled);
   }
 
   void TearDown() override {
-    if (@available(macOS 10.12.2, *)) {
-      [touch_bar_ updateWebContents:nullptr];
+    if (@available(macOS 10.12.2, *))
       touch_bar_.reset();
-    }
     CocoaProfileTest::TearDown();
   }
 
   CommandUpdater* command_updater_;  // Weak, owned by Browser.
-  content::RenderViewHostTestEnabler rvh_test_enabler_;
 
   API_AVAILABLE(macos(10.12.2))
   base::scoped_nsobject<BrowserWindowDefaultTouchBar> touch_bar_;
 };
 
-// Test if any known identifiers no longer work. See the message in the test;
-// these identifiers may be written out to disk on users' computers if they
-// customize the Touch Bar, and the corresponding items will disappear if they
-// can no longer be created.
-TEST_F(BrowserWindowDefaultTouchBarUnitTest, HistoricTouchBarItems) {
-  if (@available(macOS 10.12.2, *)) {
-    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-    for (NSString* item_identifier : {
-             @"BACK-FWD",
-             @"BACK",
-             @"FORWARD",
-             @"RELOAD-STOP",
-             @"HOME",
-             @"SEARCH",
-             @"BOOKMARK",
-             @"NEW-TAB",
-         }) {
-      auto identifier =
-          ui::GetTouchBarItemId(@"browser-window", item_identifier);
-      EXPECT_NE(nil, [touch_bar itemForIdentifier:identifier])
-          << "BrowserWindowDefaultTouchBar didn't return a Touch Bar item for "
-             "an identifier that was once available ("
-          << identifier.UTF8String
-          << "). If a user's customized Touch Bar includes this item, it will "
-             "disappear! Do not update or remove entries in this list just to "
-             "make the test pass; keep supporting old identifiers when "
-             "possible, even if they're no longer part of the set of "
-             "default/customizable items.";
-    }
-  }
-}
-
-// Tests if BrowserWindowDefaultTouchBar can produce the items it says it can
-// and, for each kind of bar, also verify that the advertised/customizable lists
-// include some representative items (if not, the lists might be wrong.)
+// Tests to check if the touch bar contains the correct items.
 TEST_F(BrowserWindowDefaultTouchBarUnitTest, TouchBarItems) {
   if (@available(macOS 10.12.2, *)) {
-    auto test_default_identifiers =
-        [&](NSSet* expected_identifiers) API_AVAILABLE(macos(10.12.2)) {
-          NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-          NSMutableSet<NSString*>* advertised_identifiers = [NSMutableSet set];
-          [advertised_identifiers
-              addObjectsFromArray:touch_bar.defaultItemIdentifiers];
-          [advertised_identifiers
-              addObjectsFromArray:touch_bar
-                                      .customizationAllowedItemIdentifiers];
-          [advertised_identifiers
-              addObjectsFromArray:touch_bar
-                                      .customizationRequiredItemIdentifiers];
-          EXPECT_TRUE(
-              [expected_identifiers isSubsetOfSet:advertised_identifiers])
-              << "Didn't find the expected identifiers "
-              << expected_identifiers.description.UTF8String
-              << " in the set of advertised identifiers "
-              << advertised_identifiers.description.UTF8String << ".";
-          for (NSString* identifier in advertised_identifiers) {
-            EXPECT_NE(nil, [touch_bar itemForIdentifier:identifier])
-                << "Didn't get a touch bar item for " << identifier.UTF8String;
-          }
-        };
-
     // Set to tab fullscreen.
     FullscreenController* fullscreen_controller =
         browser()->exclusive_access_manager()->fullscreen_controller();
     fullscreen_controller->set_is_tab_fullscreen_for_testing(true);
     EXPECT_TRUE(fullscreen_controller->IsTabFullscreen());
 
-    // The fullscreen Touch Bar should include *at least* these items.
-    test_default_identifiers([NSSet setWithArray:@[
-      BrowserWindowDefaultTouchBar.fullscreenOriginItemIdentifier,
-    ]]);
+    // The touch bar should only contain an item that displays the origin of the
+    // tab content fullscreen.
+    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
+    NSArray* touch_bar_items = [touch_bar itemIdentifiers];
+    EXPECT_TRUE(
+        [touch_bar_items containsObject:GetFullscreenTouchBarItemId(
+                                            kFullscreenOriginLabelTouchId)]);
+    EXPECT_EQ(1u, [touch_bar_items count]);
 
     // Exit fullscreen.
     fullscreen_controller->set_is_tab_fullscreen_for_testing(false);
     EXPECT_FALSE(fullscreen_controller->IsTabFullscreen());
 
-    // The default Touch Bar should include *at least* these items.
-    test_default_identifiers([NSSet setWithArray:@[
-      BrowserWindowDefaultTouchBar.backItemIdentifier,
-      BrowserWindowDefaultTouchBar.forwardItemIdentifier,
-      BrowserWindowDefaultTouchBar.reloadOrStopItemIdentifier,
-    ]]);
+    PrefService* prefs = profile()->GetPrefs();
+    DCHECK(prefs);
+    prefs->SetBoolean(prefs::kShowHomeButton, true);
+    touch_bar_items = [[touch_bar_ makeTouchBar] itemIdentifiers];
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kBackForwardTouchId)]);
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kReloadOrStopTouchId)]);
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kHomeTouchId)]);
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kSearchTouchId)]);
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kStarTouchId)]);
+    EXPECT_TRUE([touch_bar_items
+        containsObject:GetBrowserTouchBarItemId(kNewTabTouchId)]);
   }
 }
 
@@ -155,69 +131,56 @@
     NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
     [touch_bar_ setIsPageLoading:NO];
 
-    NSTouchBarItem* item =
-        [touch_bar itemForIdentifier:BrowserWindowDefaultTouchBar
-                                         .reloadOrStopItemIdentifier];
+    NSTouchBarItem* item = [touch_bar_
+                     touchBar:touch_bar
+        makeItemForIdentifier:GetBrowserTouchBarItemId(kReloadOrStopTouchId)];
     EXPECT_EQ(IDC_RELOAD, [[item view] tag]);
 
     [touch_bar_ setIsPageLoading:YES];
-    item = [touch_bar itemForIdentifier:BrowserWindowDefaultTouchBar
-                                            .reloadOrStopItemIdentifier];
+    item = [touch_bar_ touchBar:touch_bar
+          makeItemForIdentifier:GetBrowserTouchBarItemId(kReloadOrStopTouchId)];
     EXPECT_EQ(IDC_STOP, [[item view] tag]);
   }
 }
 
-// Tests if the back button on the touch bar is in sync with the back command.
-TEST_F(BrowserWindowDefaultTouchBarUnitTest, BackCommandUpdate) {
+// Tests to see if the back/forward items on the touch bar is in sync with the
+// back and forward commands.
+TEST_F(BrowserWindowDefaultTouchBarUnitTest, BackForwardCommandUpdate) {
   if (@available(macOS 10.12.2, *)) {
-    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-    NSTouchBarItem* item = [touch_bar
-        itemForIdentifier:BrowserWindowDefaultTouchBar.backItemIdentifier];
-    NSButton* button = base::mac::ObjCCast<NSButton>(item.view);
+    NSSegmentedControl* back_forward_control = [touch_bar_ backForwardControl];
 
     UpdateCommandEnabled(IDC_BACK, true);
-    EXPECT_TRUE(button.enabled);
-    UpdateCommandEnabled(IDC_BACK, false);
-    EXPECT_FALSE(button.enabled);
-  }
-}
-
-// Tests if the forward button on the touch bar is in sync with the forward
-// command.
-TEST_F(BrowserWindowDefaultTouchBarUnitTest, ForwardCommandUpdate) {
-  if (@available(macOS 10.12.2, *)) {
-    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-    NSTouchBarItem* item = [touch_bar
-        itemForIdentifier:BrowserWindowDefaultTouchBar.forwardItemIdentifier];
-    NSButton* button = base::mac::ObjCCast<NSButton>(item.view);
-
     UpdateCommandEnabled(IDC_FORWARD, true);
-    EXPECT_TRUE(button.enabled);
+    EXPECT_TRUE([back_forward_control isEnabledForSegment:kBackSegmentIndex]);
+    EXPECT_TRUE(
+        [back_forward_control isEnabledForSegment:kForwardSegmentIndex]);
+
+    UpdateCommandEnabled(IDC_BACK, false);
+    EXPECT_FALSE([back_forward_control isEnabledForSegment:kBackSegmentIndex]);
+    EXPECT_TRUE(
+        [back_forward_control isEnabledForSegment:kForwardSegmentIndex]);
+
     UpdateCommandEnabled(IDC_FORWARD, false);
-    EXPECT_FALSE(button.enabled);
+    EXPECT_FALSE([back_forward_control isEnabledForSegment:kBackSegmentIndex]);
+    EXPECT_FALSE(
+        [back_forward_control isEnabledForSegment:kForwardSegmentIndex]);
   }
 }
 
-TEST_F(BrowserWindowDefaultTouchBarUnitTest, BackAccessibilityLabel) {
+TEST_F(BrowserWindowDefaultTouchBarUnitTest, BackForwardAccessibilityLabels) {
   if (@available(macOS 10.12.2, *)) {
-    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-    NSTouchBarItem* item = [touch_bar
-        itemForIdentifier:BrowserWindowDefaultTouchBar.backItemIdentifier];
-    id<NSAccessibility> view = item.view;
-    ASSERT_TRUE([view conformsToProtocol:@protocol(NSAccessibility)]);
-    EXPECT_NSEQ(view.accessibilityTitle,
+    NSSegmentedControl* control = touch_bar_.get().backForwardControl;
+    id<NSAccessibility> cell = NSAccessibilityUnignoredDescendant(control);
+    ASSERT_TRUE([cell conformsToProtocol:@protocol(NSAccessibility)]);
+
+    id<NSAccessibility> back = cell.accessibilityChildren[0];
+    EXPECT_TRUE([back conformsToProtocol:@protocol(NSAccessibility)]);
+    EXPECT_NSEQ(back.accessibilityTitle,
                 l10n_util::GetNSString(IDS_ACCNAME_BACK));
-  }
-}
 
-TEST_F(BrowserWindowDefaultTouchBarUnitTest, ForwardAccessibilityLabel) {
-  if (@available(macOS 10.12.2, *)) {
-    NSTouchBar* touch_bar = [touch_bar_ makeTouchBar];
-    NSTouchBarItem* item = [touch_bar
-        itemForIdentifier:BrowserWindowDefaultTouchBar.forwardItemIdentifier];
-    id<NSAccessibility> view = item.view;
-    ASSERT_TRUE([view conformsToProtocol:@protocol(NSAccessibility)]);
-    EXPECT_NSEQ(view.accessibilityTitle,
+    id<NSAccessibility> forward = cell.accessibilityChildren[1];
+    EXPECT_TRUE([forward conformsToProtocol:@protocol(NSAccessibility)]);
+    EXPECT_NSEQ(forward.accessibilityTitle,
                 l10n_util::GetNSString(IDS_ACCNAME_FORWARD));
   }
 }
diff --git a/chrome/browser/ui/views/chrome_typography_provider.cc b/chrome/browser/ui/views/chrome_typography_provider.cc
index c2655e4..ebb8c1049 100644
--- a/chrome/browser/ui/views/chrome_typography_provider.cc
+++ b/chrome/browser/ui/views/chrome_typography_provider.cc
@@ -15,7 +15,6 @@
 
 #if defined(OS_WIN)
 #include "base/win/windows_version.h"
-#include "ui/gfx/platform_font_win.h"
 #include "ui/native_theme/native_theme_win.h"
 #endif
 
@@ -111,20 +110,18 @@
 #if defined(OS_WIN)
 // static
 int ChromeTypographyProvider::GetPlatformFontHeight(int font_context) {
-  const bool direct_write_enabled =
-      gfx::PlatformFontWin::IsDirectWriteEnabled();
   const bool windows_10 = base::win::GetVersion() >= base::win::VERSION_WIN10;
   switch (font_context) {
     case CONTEXT_HEADLINE:
-      return windows_10 && direct_write_enabled ? 27 : 28;
+      return windows_10 ? 27 : 28;
     case views::style::CONTEXT_DIALOG_TITLE:
-      return windows_10 || !direct_write_enabled ? 20 : 21;
+      return windows_10 ? 20 : 21;
     case CONTEXT_BODY_TEXT_LARGE:
     case CONTEXT_TAB_HOVER_CARD_TITLE:
     case views::style::CONTEXT_MESSAGE_BOX_BODY_TEXT:
-      return direct_write_enabled ? 18 : 17;
+      return 18;
     case CONTEXT_BODY_TEXT_SMALL:
-      return windows_10 && direct_write_enabled ? 16 : 15;
+      return windows_10 ? 16 : 15;
   }
   NOTREACHED();
   return 0;
diff --git a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
index 1b6ddcd..23ec94e 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
@@ -108,7 +108,7 @@
       PasswordBubbleViewBase::manage_password_bubble());
   // A pending password with empty username should initially focus on the
   // username field.
-  EXPECT_EQ(bubble->username_field(),
+  EXPECT_EQ(bubble->GetUsernameTextfieldForTest(),
             bubble->GetFocusManager()->GetFocusedView());
   PasswordBubbleViewBase::CloseCurrentBubble();
   EXPECT_FALSE(IsBubbleShowing());
diff --git a/chrome/browser/ui/views/passwords/password_items_view.cc b/chrome/browser/ui/views/passwords/password_items_view.cc
index 6394d418..c44cf6e 100644
--- a/chrome/browser/ui/views/passwords/password_items_view.cc
+++ b/chrome/browser/ui/views/passwords/password_items_view.cc
@@ -16,6 +16,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/password_manager/core/common/password_manager_ui.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/simple_combobox_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/range/range.h"
 #include "ui/resources/grit/ui_resources.h"
@@ -23,10 +24,10 @@
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/controls/button/md_text_button.h"
+#include "ui/views/controls/editable_combobox/editable_combobox.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/link.h"
 #include "ui/views/controls/link_listener.h"
-#include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/grid_layout.h"
 
@@ -110,15 +111,23 @@
   return label;
 }
 
-std::unique_ptr<views::Textfield> CreateUsernameEditable(
-    const base::string16& initial_username) {
-  auto editable = std::make_unique<views::Textfield>();
-  editable->SetText(initial_username);
-  editable->SetAccessibleName(
+std::unique_ptr<views::EditableCombobox> CreateUsernameEditableCombobox(
+    const autofill::PasswordForm& form) {
+  std::vector<base::string16> usernames = {form.username_value};
+  for (const autofill::ValueElementPair& other_possible_username_pair :
+       form.other_possible_usernames) {
+    if (other_possible_username_pair.first != form.username_value)
+      usernames.push_back(other_possible_username_pair.first);
+  }
+  auto combobox = std::make_unique<views::EditableCombobox>(
+      std::make_unique<ui::SimpleComboboxModel>(usernames),
+      /*filter_on_edit=*/false, /*show_on_empty=*/true);
+  combobox->SetText(form.username_value);
+  combobox->SetAccessibleName(
       l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL));
   // In case of long username, ensure that the beginning of value is visible.
-  editable->SelectRange(gfx::Range(0));
-  return editable;
+  combobox->SelectRange(gfx::Range(0));
+  return combobox;
 }
 
 std::unique_ptr<views::Label> CreatePasswordLabel(
diff --git a/chrome/browser/ui/views/passwords/password_items_view.h b/chrome/browser/ui/views/passwords/password_items_view.h
index e0c43909..548287fc 100644
--- a/chrome/browser/ui/views/passwords/password_items_view.h
+++ b/chrome/browser/ui/views/passwords/password_items_view.h
@@ -16,7 +16,7 @@
 #include "ui/views/view.h"
 
 namespace views {
-class Textfield;
+class EditableCombobox;
 class Label;
 }  // namespace views
 
@@ -27,8 +27,8 @@
     const autofill::PasswordForm& form,
     int federation_message_id,
     bool is_password_visible);
-std::unique_ptr<views::Textfield> CreateUsernameEditable(
-    const base::string16& initial_username);
+std::unique_ptr<views::EditableCombobox> CreateUsernameEditableCombobox(
+    const autofill::PasswordForm& form);
 
 // A dialog for managing stored password and federated login information for a
 // specific site. A user can remove managed credentials for the site via this
diff --git a/chrome/browser/ui/views/passwords/password_pending_view.cc b/chrome/browser/ui/views/passwords/password_pending_view.cc
index 01bf5956..74ea2bfb 100644
--- a/chrome/browser/ui/views/passwords/password_pending_view.cc
+++ b/chrome/browser/ui/views/passwords/password_pending_view.cc
@@ -35,6 +35,7 @@
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/grid_layout.h"
 #include "ui/views/layout/layout_provider.h"
+#include "ui/views/view.h"
 #include "ui/views/window/dialog_client_view.h"
 
 namespace {
@@ -170,8 +171,9 @@
   return button;
 }
 
-// Creates a dropdown from |PasswordForm.all_possible_passwords|.
-std::unique_ptr<views::EditableCombobox> CreatePasswordDropdownView(
+// Creates an EditableCombobox from |PasswordForm.all_possible_passwords| or
+// even just |PasswordForm.password_value|.
+std::unique_ptr<views::EditableCombobox> CreatePasswordEditableCombobox(
     const autofill::PasswordForm& form,
     bool are_passwords_revealed) {
   DCHECK(form.federation_origin.opaque());
@@ -200,7 +202,7 @@
       is_update_bubble_(model()->state() ==
                         password_manager::ui::PENDING_PASSWORD_UPDATE_STATE),
       sign_in_promo_(nullptr),
-      username_field_(nullptr),
+      username_dropdown_(nullptr),
       password_view_button_(nullptr),
       initially_focused_view_(nullptr),
       password_dropdown_(nullptr),
@@ -225,17 +227,12 @@
     credential_view->SetEnabled(false);
     AddChildView(credential_view);
   } else {
-    if (model()->enable_editing()) {
-      views::Textfield* username_field =
-          CreateUsernameEditable(model()->GetCurrentUsername()).release();
-      username_field->set_controller(this);
-      username_field_ = username_field;
-    } else {
-      username_field_ = CreateUsernameLabel(password_form).release();
-    }
-
+    username_dropdown_ =
+        CreateUsernameEditableCombobox(password_form).release();
+    username_dropdown_->set_listener(this);
+    username_dropdown_->set_show_menu_on_next_focus(false);
     password_dropdown_ =
-        CreatePasswordDropdownView(password_form, are_passwords_revealed_)
+        CreatePasswordEditableCombobox(password_form, are_passwords_revealed_)
             .release();
 
     password_view_button_ =
@@ -244,15 +241,17 @@
     views::GridLayout* layout =
         SetLayoutManager(std::make_unique<views::GridLayout>(this));
 
-    BuildCredentialRows(layout, username_field_, password_dropdown_,
+    BuildCredentialRows(layout, username_dropdown_, password_dropdown_,
                         password_view_button_);
-    if (model()->enable_editing() &&
-        model()->pending_password().username_value.empty()) {
-      initially_focused_view_ = username_field_;
-    }
+    if (model()->pending_password().username_value.empty())
+      initially_focused_view_ = username_dropdown_;
   }
 }
 
+views::View* PasswordPendingView::GetUsernameTextfieldForTest() const {
+  return username_dropdown_->GetTextfieldForTest();
+}
+
 PasswordPendingView::~PasswordPendingView() = default;
 
 bool PasswordPendingView::Accept() {
@@ -289,8 +288,8 @@
   TogglePasswordVisibility();
 }
 
-void PasswordPendingView::ContentsChanged(views::Textfield* sender,
-                                          const base::string16& new_contents) {
+void PasswordPendingView::OnContentChanged(
+    views::EditableCombobox* editable_combobox) {
   bool is_update_before = model()->IsCurrentStateUpdate();
   UpdateUsernameAndPasswordInModel();
   // May be the buttons should be updated.
@@ -379,18 +378,12 @@
 }
 
 void PasswordPendingView::UpdateUsernameAndPasswordInModel() {
-  const bool username_editable = model()->enable_editing();
-  if (!username_editable && !password_dropdown_)
-    return;
-
+  DCHECK(username_dropdown_ && password_dropdown_);
   base::string16 new_username = model()->pending_password().username_value;
   base::string16 new_password = model()->pending_password().password_value;
-  if (username_editable) {
-    new_username = static_cast<views::Textfield*>(username_field_)->text();
-    base::TrimString(new_username, base::ASCIIToUTF16(" "), &new_username);
-  }
-  if (password_dropdown_)
-    new_password = password_dropdown_->GetText();
+  new_username = username_dropdown_->GetText();
+  base::TrimString(new_username, base::ASCIIToUTF16(" "), &new_username);
+  new_password = password_dropdown_->GetText();
   model()->OnCredentialEdited(std::move(new_username), std::move(new_password));
 }
 
diff --git a/chrome/browser/ui/views/passwords/password_pending_view.h b/chrome/browser/ui/views/passwords/password_pending_view.h
index 6b58ea0..194c58ac 100644
--- a/chrome/browser/ui/views/passwords/password_pending_view.h
+++ b/chrome/browser/ui/views/passwords/password_pending_view.h
@@ -7,7 +7,7 @@
 
 #include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
 #include "ui/views/controls/button/button.h"
-#include "ui/views/controls/textfield/textfield_controller.h"
+#include "ui/views/controls/editable_combobox/editable_combobox_listener.h"
 #include "ui/views/view.h"
 
 namespace views {
@@ -22,16 +22,14 @@
 // "Save"/"Update" button and a "Never"/"Nope" button.
 class PasswordPendingView : public PasswordBubbleViewBase,
                             public views::ButtonListener,
-                            public views::TextfieldController {
+                            public views::EditableComboboxListener {
  public:
   PasswordPendingView(content::WebContents* web_contents,
                       views::View* anchor_view,
                       const gfx::Point& anchor_point,
                       DisplayReason reason);
 
-#if defined(UNIT_TEST)
-  const View* username_field() const { return username_field_; }
-#endif
+  views::View* GetUsernameTextfieldForTest() const;
 
  private:
   ~PasswordPendingView() override;
@@ -39,9 +37,8 @@
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
-  // views::TextfieldController:
-  void ContentsChanged(views::Textfield* sender,
-                       const base::string16& new_contents) override;
+  // views::EditableComboboxListener:
+  void OnContentChanged(views::EditableCombobox* editable_combobox) override;
 
   // PasswordBubbleViewBase:
   views::View* CreateFootnoteView() override;
@@ -72,7 +69,7 @@
   // active.
   PasswordSignInPromoView* sign_in_promo_;
 
-  views::View* username_field_;
+  views::EditableCombobox* username_dropdown_;
   views::ToggleImageButton* password_view_button_;
   views::View* initially_focused_view_;
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
index 6718746..8a65dc4 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
@@ -9,6 +9,7 @@
 
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/tabs/tab_style.h"
@@ -36,6 +37,10 @@
 #include "ui/views/view_class_properties.h"
 #include "ui/views/widget/widget.h"
 
+#if defined(OS_WIN)
+#include "ui/base/win/shell.h"
+#endif
+
 namespace {
 
 // Hover card and preview image dimensions.
@@ -86,6 +91,14 @@
   }
 }
 
+bool CustomShadowsSupported() {
+#if defined(OS_WIN)
+  return ui::win::IsAeroGlassEnabled();
+#else
+  return true;
+#endif
+}
+
 }  // namespace
 
 // static
@@ -239,8 +252,11 @@
 
   GetBubbleFrameView()->set_preferred_arrow_adjustment(
       views::BubbleFrameView::PreferredArrowAdjustment::kOffset);
-  GetBubbleFrameView()->bubble_border()->SetCornerRadius(
-      ChromeLayoutProvider::Get()->GetCornerRadiusMetric(views::EMPHASIS_HIGH));
+
+  if (CustomShadowsSupported())
+    GetBubbleFrameView()->bubble_border()->SetCornerRadius(
+        ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
+            views::EMPHASIS_HIGH));
 }
 
 TabHoverCardBubbleView::~TabHoverCardBubbleView() = default;
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 228d3150..f936e05 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -53,7 +53,7 @@
 #include "chrome/browser/ui/webui/policy_ui.h"
 #include "chrome/browser/ui/webui/predictors/predictors_ui.h"
 #include "chrome/browser/ui/webui/quota_internals/quota_internals_ui.h"
-#include "chrome/browser/ui/webui/settings/md_settings_ui.h"
+#include "chrome/browser/ui/webui/settings/settings_ui.h"
 #include "chrome/browser/ui/webui/settings_utils.h"
 #include "chrome/browser/ui/webui/signin_internals_ui.h"
 #include "chrome/browser/ui/webui/supervised_user_internals_ui.h"
@@ -470,7 +470,7 @@
     return &NewWebUI<NewTabUI>;
   // Settings are implemented with native UI elements on Android.
   if (url.host_piece() == chrome::kChromeUISettingsHost)
-    return &NewWebUI<settings::MdSettingsUI>;
+    return &NewWebUI<settings::SettingsUI>;
   if (url.host_piece() == chrome::kChromeUIExtensionsHost)
     return &NewWebUI<extensions::ExtensionsUI>;
   if (url.host_piece() == chrome::kChromeUIHistoryHost)
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 9918d89..d0fec1d 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -428,15 +428,6 @@
                IDS_ENTERPRISE_ENROLLMENT_AUTH_FATAL_ERROR);
   builder->Add("insecureURLEnrollmentError",
                IDS_ENTERPRISE_ENROLLMENT_AUTH_INSECURE_URL_ERROR);
-
-  builder->Add("unrecoverableCryptohomeErrorMessageTitle",
-               IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_TITLE);
-  builder->Add("unrecoverableCryptohomeErrorMessage",
-               IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_MESSAGE);
-  builder->Add("unrecoverableCryptohomeErrorContinue",
-               IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_CONTINUE);
-  builder->Add("unrecoverableCryptohomeErrorRecreatingProfile",
-               IDS_LOGIN_UNRECOVERABLE_CRYPTOHOME_ERROR_WAIT_MESSAGE);
 }
 
 void SigninScreenHandler::RegisterMessages() {
@@ -483,8 +474,6 @@
   AddCallback("maxIncorrectPasswordAttempts",
               &SigninScreenHandler::HandleMaxIncorrectPasswordAttempts);
   AddCallback("sendFeedback", &SigninScreenHandler::HandleSendFeedback);
-  AddCallback("sendFeedbackAndResyncUserData",
-              &SigninScreenHandler::HandleSendFeedbackAndResyncUserData);
 }
 
 void SigninScreenHandler::Show(const LoginScreenContext& context,
@@ -978,10 +967,6 @@
   gaia_screen_handler_->ShowWhitelistCheckFailedError();
 }
 
-void SigninScreenHandler::ShowUnrecoverableCrypthomeErrorDialog() {
-  CallJS("login.UnrecoverableCryptohomeErrorScreen.show");
-}
-
 void SigninScreenHandler::Observe(int type,
                                   const content::NotificationSource& source,
                                   const content::NotificationDetails& details) {
@@ -1416,21 +1401,6 @@
                                     weak_factory_.GetWeakPtr()));
 }
 
-void SigninScreenHandler::HandleSendFeedbackAndResyncUserData() {
-  const std::string description = base::StringPrintf(
-      "Auto generated feedback for http://crbug.com/547857.\n"
-      "(uniquifier:%s)",
-      base::NumberToString(base::Time::Now().ToInternalValue()).c_str());
-
-  login_feedback_ =
-      std::make_unique<LoginFeedback>(Profile::FromWebUI(web_ui()));
-  login_feedback_->Request(
-      description,
-      base::BindOnce(
-          &SigninScreenHandler::OnUnrecoverableCryptohomeFeedbackFinished,
-          weak_factory_.GetWeakPtr()));
-}
-
 bool SigninScreenHandler::AllWhitelistedUsersPresent() {
   CrosSettings* cros_settings = CrosSettings::Get();
   bool allow_new_user = false;
@@ -1494,15 +1464,6 @@
   login_feedback_.reset();
 }
 
-void SigninScreenHandler::OnUnrecoverableCryptohomeFeedbackFinished() {
-  CallJS("login.UnrecoverableCryptohomeErrorScreen.resumeAfterFeedbackUI");
-
-  // Recreate user's cryptohome after the feedback is attempted.
-  HandleResyncUserData();
-
-  login_feedback_.reset();
-}
-
 void SigninScreenHandler::OnAllowedInputMethodsChanged() {
   if (!webui_visible_)
     return;
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index ea5ef06..9256d90 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -92,7 +92,6 @@
   virtual void ShowPasswordChangedDialog(bool show_password_error,
                                          const std::string& email) = 0;
   virtual void ShowWhitelistCheckFailedError() = 0;
-  virtual void ShowUnrecoverableCrypthomeErrorDialog() = 0;
   virtual void LoadUsers(const user_manager::UserList& users,
                          const base::ListValue& users_list) = 0;
 
@@ -295,7 +294,6 @@
                                  const std::string& email) override;
   void ShowErrorScreen(LoginDisplay::SigninError error_id) override;
   void ShowWhitelistCheckFailedError() override;
-  void ShowUnrecoverableCrypthomeErrorDialog() override;
   void LoadUsers(const user_manager::UserList& users,
                  const base::ListValue& users_list) override;
 
@@ -371,7 +369,6 @@
   void HandleFirstIncorrectPasswordAttempt(const AccountId& account_id);
   void HandleMaxIncorrectPasswordAttempts(const AccountId& account_id);
   void HandleSendFeedback();
-  void HandleSendFeedbackAndResyncUserData();
 
   // Implements user sign-in.
   void AuthenticateExistingUser(const AccountId& account_id,
diff --git a/chrome/browser/ui/webui/management_ui_handler.cc b/chrome/browser/ui/webui/management_ui_handler.cc
index bff56ce..f87c2b67 100644
--- a/chrome/browser/ui/webui/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management_ui_handler.cc
@@ -34,9 +34,9 @@
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
-#include "chrome/browser/chromeos/policy/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/policy_cert_service.h"
 #include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
+#include "chrome/browser/chromeos/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/chromeos/policy/status_uploader.h"
 #include "chrome/browser/chromeos/policy/system_log_uploader.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -210,24 +210,24 @@
   if (!manager)
     return;
 
-  const policy::DeviceStatusCollector* collector =
-      manager->GetStatusUploader()->device_status_collector();
+  const policy::StatusCollector* collector =
+      manager->GetStatusUploader()->status_collector();
 
   // Elements appear on the page in the order they are added.
-  if (collector->report_activity_times()) {
+  if (collector->ShouldReportActivityTimes()) {
     AddDeviceReportingElement(report_sources, kManagementReportActivityTimes,
                               DeviceReportingType::kDeviceActivity);
   } else {
-    if (collector->report_users()) {
+    if (collector->ShouldReportUsers()) {
       AddDeviceReportingElement(report_sources, kManagementReportUsers,
                                 DeviceReportingType::kSupervisedUser);
     }
   }
-  if (collector->report_hardware_status()) {
+  if (collector->ShouldReportHardwareStatus()) {
     AddDeviceReportingElement(report_sources, kManagementReportHardwareStatus,
                               DeviceReportingType::kDeviceStatistics);
   }
-  if (collector->report_network_interfaces()) {
+  if (collector->ShouldReportNetworkInterfaces()) {
     AddDeviceReportingElement(report_sources,
                               kManagementReportNetworkInterfaces,
                               DeviceReportingType::kDevice);
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index 6523fdd3..3f7e7a60 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -334,6 +334,11 @@
       base::FeatureList::IsEnabled(features::kNewPrintPreviewLayout);
   source->AddBoolean("newPrintPreviewLayoutEnabled",
                      new_print_preview_layout_enabled);
+  // The key for the string below needs to be all lowercase, since it is used
+  // as an attribute and attributes are lowercased when the page is bundled.
+  source->AddString("newprintpreviewlayout", new_print_preview_layout_enabled
+                                                 ? "new-print-preview-layout"
+                                                 : "");
 }
 
 std::vector<std::string> SetupPrintPreviewPlugin(
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
index 93289559..f5ae795 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
@@ -27,8 +27,6 @@
 #include "chrome/browser/ui/webui/settings/extension_control_handler.h"
 #include "chrome/browser/ui/webui/settings/font_handler.h"
 #include "chrome/browser/ui/webui/settings/languages_handler.h"
-#include "chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.h"
-#include "chrome/browser/ui/webui/settings/md_settings_ui.h"
 #include "chrome/browser/ui/webui/settings/on_startup_handler.h"
 #include "chrome/browser/ui/webui/settings/people_handler.h"
 #include "chrome/browser/ui/webui/settings/profile_info_handler.h"
@@ -38,9 +36,11 @@
 #include "chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_cookies_view_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_import_data_handler.h"
+#include "chrome/browser/ui/webui/settings/settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_security_key_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_startup_pages_handler.h"
+#include "chrome/browser/ui/webui/settings/settings_ui.h"
 #include "chrome/browser/ui/webui/settings/site_settings_handler.h"
 #include "chrome/browser/web_applications/system_web_app_manager.h"
 #include "chrome/common/pref_names.h"
@@ -80,7 +80,7 @@
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(chrome::kChromeUIOSSettingsHost);
 
-  ::settings::MdSettingsUI::InitOSWebUIHandlers(profile, web_ui, html_source);
+  ::settings::SettingsUI::InitOSWebUIHandlers(profile, web_ui, html_source);
 
   // TODO(jamescook): Remove after basic_page.html is forked for OS settings.
   html_source->AddBoolean("showOSSettings", true);
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
similarity index 99%
rename from chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
rename to chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 830d0a854..44e8b940 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.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/ui/webui/settings/md_settings_localized_strings_provider.h"
+#include "chrome/browser/ui/webui/settings/settings_localized_strings_provider.h"
 
 #include <string>
 
@@ -2637,7 +2637,6 @@
   html_source->AddBoolean(
       "enableExperimentalWebPlatformFeatures",
       cmd.HasSwitch(::switches::kEnableExperimentalWebPlatformFeatures));
-
 }
 
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.h b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.h
similarity index 73%
rename from chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.h
rename to chrome/browser/ui/webui/settings/settings_localized_strings_provider.h
index cfa3786..1642eb7 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.h
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
 
 class Profile;
 
@@ -21,4 +21,4 @@
 
 }  // namespace settings
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_LOCALIZED_STRINGS_PROVIDER_H_
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
similarity index 94%
rename from chrome/browser/ui/webui/settings/md_settings_ui.cc
rename to chrome/browser/ui/webui/settings/settings_ui.cc
index 9cddf44..3d8d904 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.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/ui/webui/settings/md_settings_ui.h"
+#include "chrome/browser/ui/webui/settings/settings_ui.h"
 
 #include <stddef.h>
 
@@ -31,7 +31,6 @@
 #include "chrome/browser/ui/webui/settings/downloads_handler.h"
 #include "chrome/browser/ui/webui/settings/extension_control_handler.h"
 #include "chrome/browser/ui/webui/settings/font_handler.h"
-#include "chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/settings/metrics_reporting_handler.h"
 #include "chrome/browser/ui/webui/settings/on_startup_handler.h"
 #include "chrome/browser/ui/webui/settings/people_handler.h"
@@ -42,6 +41,7 @@
 #include "chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_cookies_view_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_import_data_handler.h"
+#include "chrome/browser/ui/webui/settings/settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_security_key_handler.h"
@@ -145,7 +145,7 @@
 namespace settings {
 
 // static
-void MdSettingsUI::RegisterProfilePrefs(
+void SettingsUI::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   registry->RegisterBooleanPref(prefs::kImportDialogAutofillFormData, true);
   registry->RegisterBooleanPref(prefs::kImportDialogBookmarks, true);
@@ -154,7 +154,7 @@
   registry->RegisterBooleanPref(prefs::kImportDialogSearchEngine, true);
 }
 
-MdSettingsUI::MdSettingsUI(content::WebUI* web_ui)
+SettingsUI::SettingsUI(content::WebUI* web_ui)
     : content::WebUIController(web_ui),
       WebContentsObserver(web_ui->GetWebContents()) {
 #if BUILDFLAG(OPTIMIZE_WEBUI)
@@ -219,8 +219,7 @@
 
     AddSettingsPageUIHandler(
         std::make_unique<chromeos::settings::AccountManagerUIHandler>(
-            account_manager,
-            IdentityManagerFactory::GetForProfile(profile)));
+            account_manager, IdentityManagerFactory::GetForProfile(profile)));
     html_source->AddBoolean(
         "secondaryGoogleAccountSigninAllowed",
         profile->GetPrefs()->GetBoolean(
@@ -288,6 +287,8 @@
   html_source->AddBoolean(
       "showOSSettings",
       !base::FeatureList::IsEnabled(chromeos::features::kSplitSettings));
+#else
+  html_source->AddBoolean("showOSSettings", false);
 #endif
 
   AddSettingsPageUIHandler(
@@ -314,16 +315,16 @@
 #if BUILDFLAG(OPTIMIZE_WEBUI)
   const bool use_polymer_2 =
       base::FeatureList::IsEnabled(features::kWebUIPolymer2);
-  html_source->AddResourcePath("crisper.js", IDR_MD_SETTINGS_CRISPER_JS);
+  html_source->AddResourcePath("crisper.js", IDR_SETTINGS_CRISPER_JS);
   html_source->AddResourcePath("lazy_load.crisper.js",
-                               IDR_MD_SETTINGS_LAZY_LOAD_CRISPER_JS);
-  html_source->AddResourcePath(
-      "lazy_load.html", use_polymer_2
-                            ? IDR_MD_SETTINGS_LAZY_LOAD_VULCANIZED_P2_HTML
-                            : IDR_MD_SETTINGS_LAZY_LOAD_VULCANIZED_HTML);
+                               IDR_SETTINGS_LAZY_LOAD_CRISPER_JS);
+  html_source->AddResourcePath("lazy_load.html",
+                               use_polymer_2
+                                   ? IDR_SETTINGS_LAZY_LOAD_VULCANIZED_P2_HTML
+                                   : IDR_SETTINGS_LAZY_LOAD_VULCANIZED_HTML);
   html_source->SetDefaultResource(use_polymer_2
-                                      ? IDR_MD_SETTINGS_VULCANIZED_P2_HTML
-                                      : IDR_MD_SETTINGS_VULCANIZED_HTML);
+                                      ? IDR_SETTINGS_VULCANIZED_P2_HTML
+                                      : IDR_SETTINGS_VULCANIZED_HTML);
   html_source->UseGzip(base::BindRepeating(
       [](const std::vector<std::string>& excluded_paths,
          const std::string& path) {
@@ -331,7 +332,7 @@
       },
       std::move(exclude_from_gzip)));
 #if defined(OS_CHROMEOS)
-  html_source->AddResourcePath("manifest.json", IDR_MD_SETTINGS_MANIFEST);
+  html_source->AddResourcePath("manifest.json", IDR_SETTINGS_MANIFEST);
 #endif  // defined (OS_CHROMEOS)
 #else
   // Add all settings resources.
@@ -351,15 +352,15 @@
                                 html_source);
 }
 
-MdSettingsUI::~MdSettingsUI() {}
+SettingsUI::~SettingsUI() {}
 
-void MdSettingsUI::AddSettingsPageUIHandler(
+void SettingsUI::AddSettingsPageUIHandler(
     std::unique_ptr<content::WebUIMessageHandler> handler) {
   DCHECK(handler);
   web_ui()->AddMessageHandler(std::move(handler));
 }
 
-void MdSettingsUI::DidStartNavigation(
+void SettingsUI::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
   if (navigation_handle->IsSameDocument())
     return;
@@ -367,22 +368,22 @@
   load_start_time_ = base::Time::Now();
 }
 
-void MdSettingsUI::DocumentLoadedInFrame(
+void SettingsUI::DocumentLoadedInFrame(
     content::RenderFrameHost* render_frame_host) {
   UMA_HISTOGRAM_TIMES("Settings.LoadDocumentTime.MD",
                       base::Time::Now() - load_start_time_);
 }
 
-void MdSettingsUI::DocumentOnLoadCompletedInMainFrame() {
+void SettingsUI::DocumentOnLoadCompletedInMainFrame() {
   UMA_HISTOGRAM_TIMES("Settings.LoadCompletedTime.MD",
                       base::Time::Now() - load_start_time_);
 }
 
 #if defined(OS_CHROMEOS)
 // static
-void MdSettingsUI::InitOSWebUIHandlers(Profile* profile,
-                                       content::WebUI* web_ui,
-                                       content::WebUIDataSource* html_source) {
+void SettingsUI::InitOSWebUIHandlers(Profile* profile,
+                                     content::WebUI* web_ui,
+                                     content::WebUIDataSource* html_source) {
   web_ui->AddMessageHandler(
       std::make_unique<chromeos::settings::AccessibilityHandler>(web_ui));
   web_ui->AddMessageHandler(
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.h b/chrome/browser/ui/webui/settings/settings_ui.h
similarity index 74%
rename from chrome/browser/ui/webui/settings/md_settings_ui.h
rename to chrome/browser/ui/webui/settings/settings_ui.h
index 73df051..c5c2fc9f 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.h
+++ b/chrome/browser/ui/webui/settings/settings_ui.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_UI_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_UI_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_UI_H_
 
 #include "base/macros.h"
 #include "base/time/time.h"
@@ -16,7 +16,7 @@
 namespace content {
 class WebUIDataSource;
 class WebUIMessageHandler;
-}
+}  // namespace content
 
 namespace user_prefs {
 class PrefRegistrySyncable;
@@ -25,13 +25,13 @@
 namespace settings {
 
 // The WebUI handler for chrome://settings.
-class MdSettingsUI : public content::WebUIController,
-                     public content::WebContentsObserver {
+class SettingsUI : public content::WebUIController,
+                   public content::WebContentsObserver {
  public:
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  explicit MdSettingsUI(content::WebUI* web_ui);
-  ~MdSettingsUI() override;
+  explicit SettingsUI(content::WebUI* web_ui);
+  ~SettingsUI() override;
 
 #if defined(OS_CHROMEOS)
   // Initializes the WebUI message handlers for OS-specific settings.
@@ -44,7 +44,7 @@
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
   void DocumentLoadedInFrame(
-      content::RenderFrameHost *render_frame_host) override;
+      content::RenderFrameHost* render_frame_host) override;
   void DocumentOnLoadCompletedInMainFrame() override;
 
  private:
@@ -53,9 +53,9 @@
 
   base::Time load_start_time_;
 
-  DISALLOW_COPY_AND_ASSIGN(MdSettingsUI);
+  DISALLOW_COPY_AND_ASSIGN(SettingsUI);
 };
 
 }  // namespace settings
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_MD_SETTINGS_UI_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_UI_H_
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui_browsertest.cc b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
similarity index 89%
rename from chrome/browser/ui/webui/settings/md_settings_ui_browsertest.cc
rename to chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
index da53ce1..459f2165 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
@@ -15,11 +15,11 @@
 #include "content/public/common/url_constants.h"
 #include "url/gurl.h"
 
-typedef InProcessBrowserTest MdSettingsUITest;
+typedef InProcessBrowserTest SettingsUITest;
 
 using ui_test_utils::NavigateToURL;
 
-IN_PROC_BROWSER_TEST_F(MdSettingsUITest, ViewSourceDoesntCrash) {
+IN_PROC_BROWSER_TEST_F(SettingsUITest, ViewSourceDoesntCrash) {
   NavigateToURL(browser(),
                 GURL(content::kViewSourceScheme + std::string(":") +
                      chrome::kChromeUISettingsURL + std::string("strings.js")));
@@ -27,7 +27,7 @@
 
 // Catch lifetime issues in message handlers. There was previously a problem
 // with PrefMember calling Init again after Destroy.
-IN_PROC_BROWSER_TEST_F(MdSettingsUITest, ToggleJavaScript) {
+IN_PROC_BROWSER_TEST_F(SettingsUITest, ToggleJavaScript) {
   NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL));
 
   const auto& handlers = *browser()
diff --git a/chrome/credential_provider/gaiacp/associated_user_validator.cc b/chrome/credential_provider/gaiacp/associated_user_validator.cc
index ebef2f3f..dfb8c70 100644
--- a/chrome/credential_provider/gaiacp/associated_user_validator.cc
+++ b/chrome/credential_provider/gaiacp/associated_user_validator.cc
@@ -153,6 +153,18 @@
       last_update(update_time),
       pending_query_thread(thread_handle) {}
 
+AssociatedUserValidator::ScopedBlockDenyAccessUpdate::
+    ScopedBlockDenyAccessUpdate(AssociatedUserValidator* validator)
+    : validator_(validator) {
+  DCHECK(validator_);
+  validator_->BlockDenyAccessUpdate();
+}
+
+AssociatedUserValidator::ScopedBlockDenyAccessUpdate::
+    ~ScopedBlockDenyAccessUpdate() {
+  validator_->UnblockDenyAccessUpdate();
+}
+
 // static
 AssociatedUserValidator* AssociatedUserValidator::Get() {
   return *GetInstanceStorage();
@@ -215,14 +227,12 @@
   return S_OK;
 }
 
-std::set<base::string16> AssociatedUserValidator::GetUpdatedAssociatedSids() {
+size_t AssociatedUserValidator::GetAssociatedUsersCount() {
+  base::AutoLock locker(validator_lock_);
+
   UpdateAssociatedSids(nullptr);
 
-  std::set<base::string16> associated_sids;
-  for (const auto& it : user_to_token_handle_info_)
-    associated_sids.insert(it.first);
-
-  return associated_sids;
+  return user_to_token_handle_info_.size();
 }
 
 bool AssociatedUserValidator::IsUserAccessBlockingEnforced(
@@ -239,37 +249,48 @@
   return true;
 }
 
-void AssociatedUserValidator::DenySigninForUsersWithInvalidTokenHandles(
+bool AssociatedUserValidator::DenySigninForUsersWithInvalidTokenHandles(
     CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) {
+  base::AutoLock locker(validator_lock_);
+
+  if (block_deny_access_update_)
+    return false;
+
   if (!IsUserAccessBlockingEnforced(cpus))
-    return;
+    return false;
 
   HRESULT hr = UpdateAssociatedSids(nullptr);
   if (FAILED(hr)) {
     LOGFN(ERROR) << "UpdateAssociatedSids hr=" << putHR(hr);
-    return;
+    return false;
   }
 
   auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);
 
+  bool user_denied_signin = false;
   for (const auto& user_info : user_to_token_handle_info_) {
     const base::string16& sid = user_info.first;
     if (locked_user_sids_.find(sid) != locked_user_sids_.end())
       continue;
 
-    if (!IsTokenHandleValidForUser(sid)) {
+    if (!IsTokenHandleValidForUserInternal(sid)) {
       LOGFN(INFO) << "Revoking access for sid=" << sid;
       HRESULT hr = ModifyUserAccess(policy, sid, false);
       if (FAILED(hr)) {
         LOGFN(ERROR) << "ModifyUserAccess sid=" << sid << " hr=" << putHR(hr);
       } else {
         locked_user_sids_.insert(sid);
+        user_denied_signin = true;
       }
     }
   }
+
+  return user_denied_signin;
 }
 
 HRESULT AssociatedUserValidator::RestoreUserAccess(const base::string16& sid) {
+  base::AutoLock locker(validator_lock_);
+
   if (locked_user_sids_.erase(sid)) {
     auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);
     return ModifyUserAccess(policy, sid, true);
@@ -280,6 +301,8 @@
 
 void AssociatedUserValidator::AllowSigninForAllAssociatedUsers(
     CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) {
+  base::AutoLock locker(validator_lock_);
+
   if (!MdmEnrollmentEnabled() ||
       !CGaiaCredentialProvider::IsUsageScenarioSupported(cpus))
     return;
@@ -299,6 +322,8 @@
 }
 
 void AssociatedUserValidator::AllowSigninForUsersWithInvalidTokenHandles() {
+  base::AutoLock locker(validator_lock_);
+
   LOGFN(INFO);
   auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);
   for (auto& sid : locked_user_sids_) {
@@ -310,6 +335,8 @@
 }
 
 void AssociatedUserValidator::StartRefreshingTokenHandleValidity() {
+  base::AutoLock locker(validator_lock_);
+
   std::map<base::string16, base::string16> sid_to_handle;
   HRESULT hr = UpdateAssociatedSids(&sid_to_handle);
 
@@ -404,6 +431,12 @@
 
 bool AssociatedUserValidator::IsTokenHandleValidForUser(
     const base::string16& sid) {
+  base::AutoLock locker(validator_lock_);
+  return IsTokenHandleValidForUserInternal(sid);
+}
+
+bool AssociatedUserValidator::IsTokenHandleValidForUserInternal(
+    const base::string16& sid) {
   // All token handles are valid when no internet connection is available.
   if (!HasInternetConnection())
     return true;
@@ -469,4 +502,26 @@
   return validity_it->second->is_valid;
 }
 
+void AssociatedUserValidator::BlockDenyAccessUpdate() {
+  base::AutoLock locker(validator_lock_);
+  ++block_deny_access_update_;
+}
+
+void AssociatedUserValidator::UnblockDenyAccessUpdate() {
+  base::AutoLock locker(validator_lock_);
+  DCHECK(block_deny_access_update_ > 0);
+  --block_deny_access_update_;
+}
+
+bool AssociatedUserValidator::IsDenyAccessUpdateBlocked() const {
+  base::AutoLock locker(validator_lock_);
+  return block_deny_access_update_ > 0;
+}
+
+bool AssociatedUserValidator::IsUserAccessBlocked(
+    const base::string16& sid) const {
+  base::AutoLock locker(validator_lock_);
+  return locked_user_sids_.find(sid) != locked_user_sids_.end();
+}
+
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/associated_user_validator.h b/chrome/credential_provider/gaiacp/associated_user_validator.h
index f2694874..0f5cb9d 100644
--- a/chrome/credential_provider/gaiacp/associated_user_validator.h
+++ b/chrome/credential_provider/gaiacp/associated_user_validator.h
@@ -10,9 +10,10 @@
 #include <map>
 #include <set>
 
-#include <base/strings/string16.h>
-#include <base/time/time.h>
-#include <base/win/scoped_handle.h>
+#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "base/win/scoped_handle.h"
 
 #include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h"
 
@@ -20,8 +21,57 @@
 
 // Caches the current validity of token handles and updates the validity if
 // it is older than a specified validity lifetime.
+// NOTE: This class is thread safe.
+//
+// The following functions are called at a time when it is impossible for
+// the valaditor to be accessed by multiple threads. The validator will only
+// be accessed from another thread through the BackgroundTokenHandleUpdater
+// that is created in CGaiaCredentialProvider::Advise and destroyed in
+// CGaiaCredentialProvider::Unadvise:
+// StartRefreshingTokenHandleValidity: Only called on the main thread during
+// a call to DllGetClassObject.
+// IsUserAccessBlockingEnforced: Only called on the main thread in
+// CGaiaCredentialProvider::Advise and in
+// CGaiaCredentialProviderFilter::UpdateRemoteCredential.
+// AllowSigninForUsersWithInvalidTokenHandles: Only called on the main thread
+// in CGaiaCredentialProvider::FinalRelease.
+// AllowSigninForAllAssociatedUsers: Only called on the main thread in
+// CGaiaCredentialProviderFilter::Filter.
+//
+// The following functions can be called while the validator can be accessed
+// from another thread:
+// IsTokenHandleValidForUser: Called on the main thread indirectly in
+// CGaiaCredentialProvider::GetCredentialCount. Also called on the update
+// thread while checking DenySigninForUsersWithInvalidTokenHandles.
+// GetAssociatedUsersCount: Only called on the main thread indirectly in
+// CGaiaCredentialProvider::GetCredentialCount.
+// RestoreUserAccess: Only called on the main thread in
+// CGaiaCredentialBase::HandleAutologon.
+//
+// Finally the one function that can be called on the update thread is
+// DenySigninForUsersWithInvalidTokenHandles. If this function returns
+// true, it will queue a credential update which will only be executed
+// on the main thread. The update thread will then be dormant for
+// |kTokenHandleValidityLifetime| seconds and in this time the expected
+// update of the credentials on the main thread via a call to
+// CGaiaCredentialProvider::GetCredentialCount should be able to complete
+// before a new update is requested on the update thread. This timing will
+// protect the two functions IsTokenHandleValidForUser and
+// GetAssociatedUsersCount from being called by multiple threads at the same
+// time.
 class AssociatedUserValidator {
  public:
+  // Prevent update of user access through the call to
+  // DenySigninForUsersWithInvalidTokenHandles. This will be used to prevent
+  // locking out users that are in the process of signing in.
+  class ScopedBlockDenyAccessUpdate {
+   public:
+    explicit ScopedBlockDenyAccessUpdate(AssociatedUserValidator* validator);
+    ~ScopedBlockDenyAccessUpdate();
+
+   private:
+    AssociatedUserValidator* validator_;
+  };
   // Default timeout when querying token info for token handles. If a timeout
   // occurs the token handle is assumed to be valid.
   static const base::TimeDelta kDefaultTokenHandleValidationTimeout;
@@ -54,8 +104,9 @@
       CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) const;
 
   // Goes through all associated users found and denies their access to sign
-  // in to the system based on the validity of their token handle.
-  void DenySigninForUsersWithInvalidTokenHandles(
+  // in to the system based on the validity of their token handle. Returns true
+  // if a user has just been denied signin access.
+  bool DenySigninForUsersWithInvalidTokenHandles(
       CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus);
 
   // Restores the access for a user that was denied access (if applicable).
@@ -72,15 +123,27 @@
   void AllowSigninForAllAssociatedUsers(
       CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus);
 
-  // Fills |associated_sids| with the sids of all valid associated users
-  // found on this system.
-  std::set<base::string16> GetUpdatedAssociatedSids();
-  size_t GetAssociatedUsersCount() { return GetUpdatedAssociatedSids().size(); }
+  // Gets the updated count of valid associated users that exist on this system.
+  size_t GetAssociatedUsersCount();
+
+  // Returns whether the user should be locked out of sign in (only used in
+  // tests).
+  bool IsDenyAccessUpdateBlocked() const;
 
  protected:
+  // Returns the storage used for the instance pointer.
+  static AssociatedUserValidator** GetInstanceStorage();
+
   explicit AssociatedUserValidator(base::TimeDelta validation_timeout);
   virtual ~AssociatedUserValidator();
 
+  // Returns whether the user should be locked out of sign in (only used in
+  // tests).
+  bool IsUserAccessBlocked(const base::string16& sid) const;
+
+ private:
+  bool IsTokenHandleValidForUserInternal(const base::string16& sid);
+
   bool HasInternetConnection() const;
   void CheckTokenHandleValidity(
       const std::map<base::string16, base::string16>& handles_to_verify);
@@ -90,9 +153,6 @@
   HRESULT UpdateAssociatedSids(
       std::map<base::string16, base::string16>* sid_to_handle);
 
-  // Returns the storage used for the instance pointer.
-  static AssociatedUserValidator** GetInstanceStorage();
-
   // Stores information about the current state of a user's token handle.
   // This information includes:
   //   * The last token handle found for the user.
@@ -121,12 +181,27 @@
     base::win::ScopedHandle pending_query_thread;
   };
 
+  // Increments / decrements |block_deny_access_update_| to prevent denying
+  // user access when a token handle becomes invalid. Only called via a
+  // ScopedBlockDenyAccessUpdate object.
+  void BlockDenyAccessUpdate();
+  void UnblockDenyAccessUpdate();
+
   // Maps a user's sid to the token handle info associated with this user (if
   // any).
   std::map<base::string16, std::unique_ptr<TokenHandleInfo>>
       user_to_token_handle_info_;
   base::TimeDelta validation_timeout_;
   std::set<base::string16> locked_user_sids_;
+  mutable base::Lock validator_lock_;
+
+  // When |block_deny_access_update_| != 0, prevent users from being denied
+  // access when DenySigninForUsersWithInvalidTokenHandles is called. This
+  // prevents users from being locked out while signing is occurring but a token
+  // handle update is also being requested at the same time. The functions
+  // LockDenyAccessUpdate / UnlockDenyAccessUpdate are called in the lifetime of
+  // a ScopedBlockDenyAccessUpdate to update this member.
+  size_t block_deny_access_update_ = 0;
 };
 
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc b/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
index 07c4ba2..6f12e4c 100644
--- a/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
+++ b/chrome/credential_provider/gaiacp/associated_user_validator_unittests.cc
@@ -254,6 +254,47 @@
   EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
 }
 
+TEST_F(AssociatedUserValidatorTest, BlockDenyUserAccess) {
+  FakeAssociatedUserValidator validator;
+
+  ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
+
+  CComBSTR sid;
+  ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
+                      L"username", L"password", L"fullname", L"comment",
+                      L"gaia-id", base::string16(), &sid));
+
+  // Invalid token fetch result.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(AssociatedUserValidator::kTokenInfoUrl),
+      FakeWinHttpUrlFetcher::Headers(), "{}");
+
+  validator.StartRefreshingTokenHandleValidity();
+
+  // Apply two levels blocks to deny access. This should prevent users from
+  // being blocked from accessing the system.
+  {
+    AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_blocker_outer(
+        &validator);
+    {
+      AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_blocker_inner(
+          &validator);
+      EXPECT_FALSE(
+          validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+      EXPECT_FALSE(validator.IsUserAccessBlocked(OLE2W(sid)));
+    }
+
+    EXPECT_FALSE(
+        validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+    EXPECT_FALSE(validator.IsUserAccessBlocked(OLE2W(sid)));
+  }
+  // Unblock deny access. User should not be blocked.
+  EXPECT_TRUE(validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+  EXPECT_TRUE(validator.IsUserAccessBlocked(OLE2W(sid)));
+
+  EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
+}
+
 // Tests various scenarios where user access is blocked.
 // Parameters are:
 // 1. CREDENTIAL_PROVIDER_USAGE_SCENARIO - Usage scenario.
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index 86bfaf5..3011c1b 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -649,6 +649,8 @@
     events_->SetFieldSubmitButton(this, FID_SUBMIT, FID_DESCRIPTION);
     UpdateSubmitButtonInteractiveState();
   }
+
+  token_update_locker_.reset();
 }
 
 HRESULT CGaiaCredentialBase::GetBaseGlsCommandline(
@@ -810,19 +812,11 @@
     }
   }
 
-  // Restore user's access so that they can sign in.
-  HRESULT hr =
-      AssociatedUserValidator::Get()->RestoreUserAccess(OLE2W(get_sid()));
-  if (FAILED(hr) && hr != HRESULT_FROM_NT(STATUS_OBJECT_NAME_NOT_FOUND)) {
-    LOGFN(ERROR) << "RestoreUserAccess hr=" << putHR(hr);
-    return hr;
-  }
-
   // The OS user has already been created, so return all the information
   // needed to log them in.
   DWORD cpus = 0;
   provider()->GetUsageScenario(&cpus);
-  hr = BuildCredPackAuthenticationBuffer(
+  HRESULT hr = BuildCredPackAuthenticationBuffer(
       domain_, get_username(), get_password(),
       static_cast<CREDENTIAL_PROVIDER_USAGE_SCENARIO>(cpus), cpcs);
   if (FAILED(hr)) {
@@ -830,6 +824,23 @@
     return hr;
   }
 
+  // Prevent update of token handle validity until after sign in has completed
+  // so that a race condition doesn't end up locking out a user while they are
+  // in the process of signing in. The lock must occur before restoring access
+  // to the user below to prevent a race condition where the user would have
+  // their access restored but then the token handle update thread is
+  // immediately executed which causes the user to be locked again afterwards.
+  PreventDenyAccessUpdate();
+
+  // Restore user's access so that they can sign in.
+  hr = AssociatedUserValidator::Get()->RestoreUserAccess(OLE2W(get_sid()));
+  if (FAILED(hr) && hr != HRESULT_FROM_NT(STATUS_OBJECT_NAME_NOT_FOUND)) {
+    LOGFN(ERROR) << "RestoreUserAccess hr=" << putHR(hr);
+    ::CoTaskMemFree(cpcs->rgbSerialization);
+    cpcs->rgbSerialization = nullptr;
+    return hr;
+  }
+
   cpcs->clsidCredentialProvider = CLSID_GaiaCredentialProvider;
   *cpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
 
@@ -857,6 +868,14 @@
 #endif  // defined(GOOGLE_CHROME_BUILD)
 }
 
+void CGaiaCredentialBase::PreventDenyAccessUpdate() {
+  if (!token_update_locker_) {
+    token_update_locker_.reset(
+        new AssociatedUserValidator::ScopedBlockDenyAccessUpdate(
+            AssociatedUserValidator::Get()));
+  }
+}
+
 // static
 BSTR CGaiaCredentialBase::AllocErrorString(UINT id) {
   CComBSTR str(GetStringResource(id).c_str());
@@ -1063,6 +1082,7 @@
 
   HRESULT hr = HandleAutologon(cpgsr, cpcs);
 
+  bool submit_button_enabled = false;
   // Don't clear the state of the credential on error. The error can occur
   // because the user is locked out or entered an incorrect old password when
   // trying to update their password. In these situations it may still be
@@ -1104,7 +1124,7 @@
         *status_icon = CPSI_NONE;
         *cpgsr = CPGSR_NO_CREDENTIAL_FINISHED;
         LOGFN(INFO) << "No internet connection";
-        UpdateSubmitButtonInteractiveState();
+        submit_button_enabled = UpdateSubmitButtonInteractiveState();
 
         hr = S_OK;
       } else {
@@ -1131,9 +1151,15 @@
           this, FID_CURRENT_PASSWORD_FIELD,
           needs_windows_password_ ? CPFIS_FOCUSED : CPFIS_NONE);
     }
-    UpdateSubmitButtonInteractiveState();
+    submit_button_enabled = UpdateSubmitButtonInteractiveState();
   }
-  // Otherwise, keep the ui disable forever now. ReportResult will eventually
+
+  // If user interaction is enabled that means we are not trying to do final
+  // sign in of the account so we can re-enable token updates.
+  if (submit_button_enabled)
+    token_update_locker_.reset();
+
+  // Otherwise, keep the ui disabled forever now. ReportResult will eventually
   // be called on success or failure and the reset of the state of the
   // credential will be done there.
   return hr;
@@ -1746,6 +1772,11 @@
 
   result_status_ = STATUS_SUCCESS;
 
+  // Prevent update of token handle validity until after sign in has completed
+  // so the list of credentials doesn't suddenly change between now and when the
+  // attempt to auto login occurs.
+  PreventDenyAccessUpdate();
+
   // When this function returns, winlogon will be told to logon to the newly
   // created account.  This is important, as the save account info process
   // can't actually save the info until the user's profile is created, which
@@ -1775,15 +1806,17 @@
                                         CComBSTR(), FALSE);
 }
 
-void CGaiaCredentialBase::UpdateSubmitButtonInteractiveState() {
+bool CGaiaCredentialBase::UpdateSubmitButtonInteractiveState() {
+  bool should_enable =
+      logon_ui_process_ == INVALID_HANDLE_VALUE &&
+      ((!needs_windows_password_ || current_windows_password_.Length()) ||
+       (needs_windows_password_ && request_force_password_change_));
   if (events_) {
-    bool should_enable =
-        logon_ui_process_ == INVALID_HANDLE_VALUE &&
-        ((!needs_windows_password_ || current_windows_password_.Length()) ||
-         (needs_windows_password_ && request_force_password_change_));
     events_->SetFieldInteractiveState(
         this, FID_SUBMIT, should_enable ? CPFIS_NONE : CPFIS_DISABLED);
   }
+
+  return should_enable;
 }
 
 void CGaiaCredentialBase::DisplayPasswordField(int password_message) {
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.h b/chrome/credential_provider/gaiacp/gaia_credential_base.h
index 44ca403..d4848a4 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.h
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.h
@@ -13,6 +13,7 @@
 #include "base/values.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_process_information.h"
+#include "chrome/credential_provider/gaiacp/associated_user_validator.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h"
 #include "chrome/credential_provider/gaiacp/gcp_utils.h"
 #include "chrome/credential_provider/gaiacp/scoped_handle.h"
@@ -171,6 +172,11 @@
       CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr,
       CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs);
 
+  // Instantiates |token_update_locker_| so that user access cannot be denied
+  // during this time. This function is called when we are about to really sign
+  // in the user to Windows.
+  void PreventDenyAccessUpdate();
+
   // Writes value to omaha registry to record that GCP has been used.
   static void TellOmahaDidRun();
 
@@ -233,8 +239,8 @@
   // not require direct user input to the credential (user is entering
   // credentials in  GLS) or a submit of the credential is not valid (user needs
   // to enter the old Windows password but currently nothing has been entered in
-  // the password field).
-  void UpdateSubmitButtonInteractiveState();
+  // the password field). Returns true if the submit button is enabled.
+  bool UpdateSubmitButtonInteractiveState();
 
   // Stops the GLS process in case it is still executing. Often called when user
   // switches credentials in the middle of a sign in through the GLS.
@@ -293,6 +299,13 @@
 
   // Holds information about the success or failure of the sign in.
   NTSTATUS result_status_ = STATUS_SUCCESS;
+
+  // When we finally want to allow user sign in. This object is instantiated
+  // to prevent updates of token handle validity until after sign in has
+  // completed so the the user cannot be locked out while they are trying to
+  // sign in.
+  std::unique_ptr<AssociatedUserValidator::ScopedBlockDenyAccessUpdate>
+      token_update_locker_;
 };
 
 }  // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 93b4930..0ede0ea 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -723,6 +723,88 @@
   EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
 }
 
+TEST_F(GcpGaiaCredentialBaseTest, DenySigninBlockedDuringSignin) {
+  USES_CONVERSION;
+
+  FakeAssociatedUserValidator validator;
+  FakeInternetAvailabilityChecker internet_checker;
+  ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
+  ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
+  GoogleMdmEnrollmentStatusForTesting force_success(true);
+
+  // Create a fake user that has the same gaia id as the test gaia id.
+  CComBSTR first_sid;
+  base::string16 username(L"foo");
+  ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
+                      username, L"password", L"name", L"comment",
+                      base::UTF8ToUTF16(kDefaultGaiaId), base::string16(),
+                      &first_sid));
+  ASSERT_EQ(2ul, fake_os_user_manager()->GetUserCount());
+  FakeGaiaCredentialProvider provider;
+
+  // Invalid token fetch result.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(AssociatedUserValidator::kTokenInfoUrl),
+      FakeWinHttpUrlFetcher::Headers(), "{}");
+
+  validator.StartRefreshingTokenHandleValidity();
+
+  // Start logon.
+  CComPtr<IGaiaCredential> gaia_cred;
+  CComPtr<ICredentialProviderCredential> cred;
+  ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, run_helper()->StartLogonProcessAndWait(cred));
+
+  // Signin process has already started. User should not be locked even if their
+  // token handle is invalid.
+  EXPECT_FALSE(validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+  EXPECT_FALSE(validator.IsUserAccessBlocked(OLE2W(first_sid)));
+
+  // Now finish the logon.
+  CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE cpgsr;
+  CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION cpcs;
+  wchar_t* status_text;
+  CREDENTIAL_PROVIDER_STATUS_ICON status_icon;
+  ASSERT_EQ(S_OK,
+            cred->GetSerialization(&cpgsr, &cpcs, &status_text, &status_icon));
+  EXPECT_EQ(nullptr, status_text);
+  EXPECT_EQ(CPSI_SUCCESS, status_icon);
+  EXPECT_EQ(CPGSR_RETURN_CREDENTIAL_FINISHED, cpgsr);
+  EXPECT_LT(0u, cpcs.cbSerialization);
+  EXPECT_NE(nullptr, cpcs.rgbSerialization);
+
+  // State was not reset.
+  EXPECT_TRUE(test->AreCredentialsValid());
+
+  // User should have been associated.
+  EXPECT_EQ(test->GetFinalUsername(), username);
+  // Email should be the same as the default one.
+  EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
+
+  // Result has not been reported yet, user signin should still not be denied.
+  EXPECT_FALSE(validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+  EXPECT_FALSE(validator.IsUserAccessBlocked(OLE2W(first_sid)));
+
+  wchar_t* report_status_text = nullptr;
+  CREDENTIAL_PROVIDER_STATUS_ICON report_icon;
+  EXPECT_EQ(S_OK, cred->ReportResult(0, 0, &report_status_text, &report_icon));
+  // State was reset.
+  EXPECT_FALSE(test->AreCredentialsValid());
+
+  EXPECT_EQ(S_OK, gaia_cred->Terminate());
+
+  // Now signin can be denied for the user if their token handle is invalid.
+  EXPECT_TRUE(validator.DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
+  EXPECT_TRUE(validator.IsUserAccessBlocked(OLE2W(first_sid)));
+
+  // No new user should be created.
+  EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
+}
+
 TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD_Gmail) {
   USES_CONVERSION;
   FakeGaiaCredentialProvider provider;
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
index ff0e21e..273faefc 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
@@ -13,6 +13,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/common/chrome_version.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
@@ -96,6 +97,176 @@
 
 }  // namespace
 
+// Class that when constructed automatically starts a thread that tries
+// to update the validity of available token handles. If the background
+// thread detects that any token handle has changed, then it will notify
+// the provider |event_handler| object of this event.
+class BackgroundTokenHandleUpdater {
+ public:
+  BackgroundTokenHandleUpdater(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
+                               ICredentialUpdateEventsHandler* event_handler);
+  ~BackgroundTokenHandleUpdater();
+
+ private:
+  static unsigned __stdcall PeriodicTokenHandleUpdate(void* param);
+
+  CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus_;
+
+  // Raw pointer to the interface on CGaiaCredentialProvider that is used
+  // to notify that token handle validity has changed. Any instance of this
+  // class should be owned by the CGaiaCredentialProvider to ensure that
+  // this pointer outlives the updater.
+  ICredentialUpdateEventsHandler* event_handler_;
+
+  base::win::ScopedHandle token_update_thread_;
+  base::WaitableEvent token_update_quit_event_;
+};
+
+BackgroundTokenHandleUpdater::BackgroundTokenHandleUpdater(
+    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
+    ICredentialUpdateEventsHandler* event_handler)
+    : cpus_(cpus), event_handler_(event_handler) {
+  unsigned wait_thread_id;
+  uintptr_t wait_thread =
+      _beginthreadex(nullptr, 0, PeriodicTokenHandleUpdate,
+                     reinterpret_cast<void*>(this), 0, &wait_thread_id);
+  if (wait_thread != 0) {
+    token_update_thread_.Set(
+        reinterpret_cast<base::win::ScopedHandle::Handle>(wait_thread));
+  }
+}
+
+BackgroundTokenHandleUpdater::~BackgroundTokenHandleUpdater() {
+  if (token_update_thread_.IsValid()) {
+    // Tell the background thread to quit and then make sure it does.  This
+    // prevents it from accessing data members that have been freed.
+    token_update_quit_event_.Signal();
+    ::WaitForSingleObject(token_update_thread_.Get(), INFINITE);
+  }
+}
+
+unsigned __stdcall BackgroundTokenHandleUpdater::PeriodicTokenHandleUpdate(
+    void* param) {
+  BackgroundTokenHandleUpdater* updater =
+      reinterpret_cast<BackgroundTokenHandleUpdater*>(param);
+  ICredentialUpdateEventsHandler* event_handler = updater->event_handler_;
+  base::WaitableEvent& stop_event = updater->token_update_quit_event_;
+
+  while (true) {
+    HRESULT hr = ::WaitForSingleObject(
+        stop_event.handle(),
+        AssociatedUserValidator::kTokenHandleValidityLifetime.InMilliseconds());
+
+    if (hr != WAIT_TIMEOUT)
+      break;
+
+    bool user_access_changed =
+        AssociatedUserValidator::Get()
+            ->DenySigninForUsersWithInvalidTokenHandles(updater->cpus_);
+    if (user_access_changed) {
+      LOGFN(INFO) << "A user token handle has been invalidated. Refreshing "
+                     "credentials";
+    }
+    event_handler->UpdateCredentialsIfNeeded(user_access_changed);
+  }
+
+  return 0;
+}
+
+CGaiaCredentialProvider::ComPtrStorage::ComPtrStorage() = default;
+CGaiaCredentialProvider::ComPtrStorage::~ComPtrStorage() = default;
+
+CGaiaCredentialProvider::ProviderConcurrentState::ProviderConcurrentState() =
+    default;
+CGaiaCredentialProvider::ProviderConcurrentState::~ProviderConcurrentState() =
+    default;
+
+bool CGaiaCredentialProvider::ProviderConcurrentState::
+    RequestUserRefreshIfNeeded(bool user_access_changed) {
+  base::AutoLock locker(state_update_lock_);
+  if (auto_logon_credential_) {
+    // Auto logon has precedence and has already signalled a credential changed,
+    // save the user refresh for a later update and don't signal the change
+    // again.
+    if (user_access_changed)
+      pending_users_refresh_needed_ = user_access_changed;
+    return false;
+  }
+
+  // No auto logon present and there is a user access change or a pending
+  // refresh to be executed. We clear the pending the refresh and just set
+  // |users_need_to_be_refreshed_| to notify that a refresh is needed on the
+  // next GetCredentialCount.
+  if (user_access_changed || pending_users_refresh_needed_) {
+    users_need_to_be_refreshed_ = true;
+    pending_users_refresh_needed_ = false;
+  }
+
+  return users_need_to_be_refreshed_;
+}
+
+bool CGaiaCredentialProvider::ProviderConcurrentState::SetAutoLogonCredential(
+    const CComPtr<IGaiaCredential>& auto_logon_credential) {
+  base::AutoLock locker(state_update_lock_);
+  // Always update the credential.
+  auto_logon_credential_ = auto_logon_credential;
+  if (auto_logon_credential_) {
+    // If a previous user refresh was signalled, then the credential changed
+    // was already sent. Don't need to send it again, but we need save the
+    // user refresh for a later credential change event since auto logon has
+    // precedence.
+    if (users_need_to_be_refreshed_) {
+      pending_users_refresh_needed_ = users_need_to_be_refreshed_;
+      users_need_to_be_refreshed_ = false;
+      return false;
+    }
+
+    return true;
+  }
+
+  // No auto logon credential was set, no need to send credential changed event.
+  return false;
+}
+
+void CGaiaCredentialProvider::ProviderConcurrentState::GetUpdatedState(
+    bool* needs_to_refresh_users,
+    ComPtrStorage* auto_logon_credential) {
+  DCHECK(needs_to_refresh_users);
+  DCHECK(auto_logon_credential);
+  base::AutoLock locker(state_update_lock_);
+
+  // States need to be mutually exclusive.
+  DCHECK(!users_need_to_be_refreshed_ || !auto_logon_credential_);
+
+  *needs_to_refresh_users = users_need_to_be_refreshed_;
+  auto_logon_credential->gaia_cred = auto_logon_credential_;
+
+  // No specific state set this cycle, maybe there was a user update pending
+  // that should now be processed.
+  if (!users_need_to_be_refreshed_ && !auto_logon_credential_) {
+    *needs_to_refresh_users = pending_users_refresh_needed_;
+    // Can now reset the pending refresh since it has been processed.
+    pending_users_refresh_needed_ = false;
+  }
+
+  // State has been extracted for use, reset back to the default state.
+  InternalReset();
+}
+
+void CGaiaCredentialProvider::ProviderConcurrentState::Reset() {
+  base::AutoLock locker(state_update_lock_);
+  InternalReset();
+
+  // This is a full explicit reset, we also need to reset any pending refresh
+  // that may be needed.
+  pending_users_refresh_needed_ = false;
+}
+
+void CGaiaCredentialProvider::ProviderConcurrentState::InternalReset() {
+  users_need_to_be_refreshed_ = false;
+  auto_logon_credential_.Release();
+}
+
 CGaiaCredentialProvider::CGaiaCredentialProvider() {}
 
 CGaiaCredentialProvider::~CGaiaCredentialProvider() {}
@@ -108,6 +279,7 @@
 
 void CGaiaCredentialProvider::FinalRelease() {
   LOGFN(INFO);
+  CHECK(!token_handle_updater_);
   ClearTransient();
   // Unlock all the users that had their access locked due to invalid token
   // handles.
@@ -125,11 +297,12 @@
 
 void CGaiaCredentialProvider::ClearTransient() {
   LOGFN(INFO);
+  CHECK(!token_handle_updater_);
   // Reset event support.
   advise_context_ = 0;
   events_.Release();
-  new_user_sid_.Empty();
-  index_ = std::numeric_limits<size_t>::max();
+  set_serialization_sid_.clear();
+  concurrent_state_.Reset();
 }
 
 void CGaiaCredentialProvider::CleanupOlderVersions() {
@@ -155,7 +328,7 @@
   if (SUCCEEDED(hr)) {
     hr = cred->Initialize(this);
     if (SUCCEEDED(hr)) {
-      AddCredentialAndCheckAutoLogon(cred, base::string16());
+      AddCredentialAndCheckAutoLogon(cred, base::string16(), nullptr);
     } else {
       LOG(ERROR) << "Could not create credential hr=" << putHR(hr);
     }
@@ -165,7 +338,8 @@
 }
 
 HRESULT CGaiaCredentialProvider::CreateReauthCredentials(
-    ICredentialProviderUserArray* users) {
+    ICredentialProviderUserArray* users,
+    ComPtrStorage* auto_logon_credential) {
   std::map<base::string16, std::pair<base::string16, base::string16>>
       sid_to_username;
 
@@ -240,7 +414,7 @@
       return hr;
     }
 
-    AddCredentialAndCheckAutoLogon(cred, sid);
+    AddCredentialAndCheckAutoLogon(cred, sid, auto_logon_credential);
   }
 
   return S_OK;
@@ -248,10 +422,14 @@
 
 void CGaiaCredentialProvider::AddCredentialAndCheckAutoLogon(
     const CComPtr<IGaiaCredential>& cred,
-    const base::string16& sid) {
+    const base::string16& sid,
+    ComPtrStorage* auto_logon_credential) {
   USES_CONVERSION;
   users_.emplace_back(cred);
 
+  if (!auto_logon_credential)
+    return;
+
   if (sid.empty())
     return;
 
@@ -267,8 +445,28 @@
   if (set_serialization_sid_ != sid)
     return;
 
-  index_ = users_.size() - 1;
-  new_user_sid_ = W2COLE(sid.c_str());
+  auto_logon_credential->gaia_cred = cred;
+  set_serialization_sid_.clear();
+}
+
+void CGaiaCredentialProvider::RecreateCredentials(
+    ComPtrStorage* auto_logon_credential) {
+  LOGFN(INFO);
+  DCHECK(user_array_);
+
+  DestroyCredentials();
+
+  CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS options;
+  HRESULT hr = user_array_->GetAccountOptions(&options);
+  bool showing_other_user =
+      SUCCEEDED(hr) && options != CPAO_EMPTY_CONNECTED && options != CPAO_NONE;
+  hr = CreateAnonymousCredentialIfNeeded(showing_other_user);
+  if (FAILED(hr))
+    LOG(ERROR) << "Could not create anonymous credential hr=" << putHR(hr);
+
+  hr = CreateReauthCredentials(user_array_, auto_logon_credential);
+  if (FAILED(hr))
+    LOG(ERROR) << "CreateReauthCredentials hr=" << putHR(hr);
 }
 
 // Static.
@@ -300,6 +498,24 @@
   return true;
 }
 
+// ICredentialUpdateEventsHandler //////////////////////////////////////////////
+
+void CGaiaCredentialProvider::UpdateCredentialsIfNeeded(
+    bool user_access_changed) {
+  // Defer refresh of the users to the next GetCredentialCount that will be
+  // called after the credentials changed event. This prevents potential
+  // contention of the |users_| list in multiple places. If the call to
+  // RequestUserRefreshIfNeeded returns true, this means we are allowed to
+  // proceed with a credentials changed event. Otherwise either no credentials
+  // need to be updated (|user_access_changed| == false) or a higher priority
+  // update is pending (e.g. auto logon) and thus we cannot send a
+  // credentials changed event.
+  if (concurrent_state_.RequestUserRefreshIfNeeded(user_access_changed) &&
+      events_) {
+    events_->CredentialsChanged(advise_context_);
+  }
+}
+
 // IGaiaCredentialProvider ////////////////////////////////////////////////////
 
 HRESULT CGaiaCredentialProvider::GetUsageScenario(DWORD* cpus) {
@@ -316,31 +532,32 @@
     BOOL fire_credentials_changed) {
   DCHECK(!credential || sid);
 
-  // |credential| should be in the |users_|.  Find its index.
-  index_ = std::numeric_limits<size_t>::max();
-  for (size_t i = 0; i < users_.size(); ++i) {
-    if (users_[i].IsEqualObject(credential)) {
-      index_ = i;
-      break;
-    }
-  }
-  if (index_ == std::numeric_limits<size_t>::max()) {
-    LOGFN(INFO) << "Could not find credential";
-    return E_INVALIDARG;
+  if (!fire_credentials_changed)
+    return S_OK;
+
+  // Ensure that user access cannot be denied at this time so that the user
+  // that is about to sign in won't be locked. If a ScopedLockDenyAccessUpdate
+  // is created before calling this function this should guarantee that
+  // situation because the call to BlockDenyAccessUpdate is locked with the
+  // same lock that is used in DenySigninForUsersWithInvalidTokenHandles.
+  // So either the call to Deny has finished and no new deny will occur
+  // afterwards or the Deny will be disabled because the block has been
+  // incremented first.
+  CHECK(!credential ||
+        AssociatedUserValidator::Get()->IsDenyAccessUpdateBlocked());
+
+  CComPtr<IGaiaCredential> gaia_credential;
+  if (credential->QueryInterface(IID_IGaiaCredential,
+                                 reinterpret_cast<void**>(&gaia_credential)) ==
+      S_OK) {
+    // Try to set the auto logon credential. If it succeeds we can raise a
+    // credential changed event.
+    if (concurrent_state_.SetAutoLogonCredential(gaia_credential) && events_)
+      events_->CredentialsChanged(advise_context_);
   }
 
-  new_user_sid_ = sid;
-
-  // Tell winlogon.exe that credential info has changed.  This provider will
-  // make the newly created user the default login credential with auto logon
-  // enabled.  See GetCredentialCount() for more details.
-  HRESULT hr = S_OK;
-  if (events_ && fire_credentials_changed)
-    hr = events_->CredentialsChanged(advise_context_);
-
-  LOGFN(INFO) << "hr=" << putHR(hr) << " sid=" << new_user_sid_.m_str
-              << " index=" << index_;
-  return hr;
+  LOGFN(INFO) << "Signing in authenticated sid=" << OLE2CW(sid);
+  return S_OK;
 }
 
 // ICredentialProviderSetUserArray ////////////////////////////////////////////
@@ -348,6 +565,7 @@
 HRESULT CGaiaCredentialProvider::SetUserArray(
     ICredentialProviderUserArray* users) {
   LOGFN(INFO);
+  CHECK(!token_handle_updater_);
 
   if (!IsUsageScenarioSupported(cpus_))
     return S_OK;
@@ -357,19 +575,11 @@
     return E_UNEXPECTED;
   }
 
-  CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS options;
-  HRESULT hr = users->GetAccountOptions(&options);
-  bool showing_other_user =
-      SUCCEEDED(hr) && options != CPAO_EMPTY_CONNECTED && options != CPAO_NONE;
-  hr = CreateAnonymousCredentialIfNeeded(showing_other_user);
-  if (FAILED(hr))
-    LOG(ERROR) << "Could not create anonymous credential hr=" << putHR(hr);
+  user_array_ = users;
+  // Force refresh of all users on the next GetCredentialCount.
+  concurrent_state_.RequestUserRefreshIfNeeded(true);
 
-  hr = CreateReauthCredentials(users);
-  if (FAILED(hr))
-    LOG(ERROR) << "CreateReauthCredentials hr=" << putHR(hr);
-
-  return hr;
+  return S_OK;
 }
 
 // ICredentialProvider ////////////////////////////////////////////////////////
@@ -378,6 +588,7 @@
     CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
     DWORD flags) {
   ClearTransient();
+  CHECK(!token_handle_updater_);
 
   cpus_ = cpus;
   cpus_flags_ = flags;
@@ -389,6 +600,7 @@
 HRESULT CGaiaCredentialProvider::SetSerialization(
     const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs) {
   DCHECK(pcpcs);
+  CHECK(!token_handle_updater_);
 
   if (pcpcs->clsidCredentialProvider != CLSID_GaiaCredentialProvider)
     return E_NOTIMPL;
@@ -416,19 +628,25 @@
 HRESULT CGaiaCredentialProvider::Advise(ICredentialProviderEvents* pcpe,
                                         UINT_PTR context) {
   DCHECK(pcpe);
+  CHECK(!token_handle_updater_);
 
-  bool had_previous = events_.p != nullptr;
   events_ = pcpe;
   advise_context_ = context;
-  LOGFN(INFO) << " had=" << had_previous;
+
+  if (AssociatedUserValidator::Get()->IsUserAccessBlockingEnforced(cpus_)) {
+    token_handle_updater_ =
+        std::make_unique<BackgroundTokenHandleUpdater>(cpus_, this);
+  }
 
   return S_OK;
 }
 
 HRESULT CGaiaCredentialProvider::UnAdvise() {
+  // Kill the updater thread (if any).
+  token_handle_updater_.reset();
+
   ClearTransient();
-  HRESULT hr = DestroyCredentials();
-  LOGFN(INFO) << "hr=" << putHR(hr);
+  DestroyCredentials();
 
   // Delete the startup sentinel file if any still exists. It can still exist in
   // 2 cases:
@@ -436,16 +654,17 @@
   // 1. The UnAdvise should only occur after the user has logged in, so if they
   // never selected any gaia credential and just used normal credentials this
   // function will be called in that situation and it is guaranteed that the
-  // user has at least been able provide some input to winlogon. 2. When no
-  // usage scenario is supported, none of the credentials will be selected and
-  // thus the gcpw startup sentinel file will not be deleted. So in the case
-  // where the user is asked for CPUS_CRED_UI enough times, the sentinel file
-  // size will keep growing without being deleted and eventually GCPW will be
-  // disabled completely. In the unsupported usage scenario, FinalRelease will
-  // be called shortly after SetUsageScenario if the function returns E_NOTIMPL
-  // so try to catch potential crashes of the destruction of the provider when
-  // it is not used because crashes in this case will prevent the cred ui from
-  // coming up and not allow the user to access their desired resource.
+  // user has at least been able provide some input to winlogon.
+  // 2. When no usage scenario is supported, none of the credentials will be
+  // selected and thus the gcpw startup sentinel file will not be deleted. So in
+  // the case where the user is asked for CPUS_CRED_UI enough times, the
+  // sentinel file size will keep growing without being deleted and eventually
+  // GCPW will be disabled completely. In the unsupported usage scenario,
+  // FinalRelease will be called shortly after SetUsageScenario if the function
+  // returns E_NOTIMPL so try to catch potential crashes of the destruction of
+  // the provider when it is not used because crashes in this case will prevent
+  // the cred ui from coming up and not allow the user to access their desired
+  // resource.
   DeleteStartupSentinel();
 
   return S_OK;
@@ -489,16 +708,35 @@
     DWORD* count,
     DWORD* default_index,
     BOOL* autologin_with_default) {
+  bool needs_to_refresh_users = false;
+  ComPtrStorage local_auto_logon_credential;
+
+  // Get the mutually exclusive state of the provider so that we can
+  // determine the correct next step (recreate credentials or auto logon).
+  concurrent_state_.GetUpdatedState(&needs_to_refresh_users,
+                                    &local_auto_logon_credential);
+
   // NOTE: assumes SetUserArray() is called before this.
+  if (needs_to_refresh_users)
+    RecreateCredentials(&local_auto_logon_credential);
+
   *count = users_.size();
   *default_index = CREDENTIAL_PROVIDER_NO_DEFAULT;
   *autologin_with_default = false;
 
   // If a user was authenticated, winlogon was notified of credentials changes
   // and is re-enumerating the credentials.  Make sure autologin is enabled.
-  if (index_ < users_.size() && new_user_sid_.Length() > 0) {
-    *default_index = index_;
-    *autologin_with_default = true;
+  if (local_auto_logon_credential.gaia_cred) {
+    // Find the index of the credential that should contain the authentication
+    // information.
+    for (size_t i = 0;
+         i < users_.size() && *default_index == CREDENTIAL_PROVIDER_NO_DEFAULT;
+         ++i) {
+      if (local_auto_logon_credential.gaia_cred.IsEqualObject(users_[i]))
+        *default_index = i;
+    }
+
+    *autologin_with_default = *default_index != CREDENTIAL_PROVIDER_NO_DEFAULT;
   }
 
   LOGFN(INFO) << " count=" << *count << " default=" << *default_index
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider.h b/chrome/credential_provider/gaiacp/gaia_credential_provider.h
index 5f33b2a..2a6db40 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider.h
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/win/scoped_handle.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential.h"
@@ -19,6 +20,16 @@
 
 namespace credential_provider {
 
+class BackgroundTokenHandleUpdater;
+
+// Event handler that can be notified when a user's access has been revoked,
+// allowing the credential provider to update the list of available credentials.
+class ICredentialUpdateEventsHandler {
+ public:
+  virtual ~ICredentialUpdateEventsHandler() = default;
+  virtual void UpdateCredentialsIfNeeded(bool user_access_changed) = 0;
+};
+
 // Implementation of ICredentialProvider backed by Gaia.
 class ATL_NO_VTABLE CGaiaCredentialProvider
     : public CComObjectRootEx<CComMultiThreadModel>,
@@ -26,14 +37,15 @@
                          &CLSID_GaiaCredentialProvider>,
       public IGaiaCredentialProvider,
       public ICredentialProviderSetUserArray,
-      public ICredentialProvider {
+      public ICredentialProvider,
+      public ICredentialUpdateEventsHandler {
  public:
   // This COM object is registered with the rgs file.  The rgs file is used by
   // CGaiaCredentialProviderModule class, see latter for details.
   DECLARE_NO_REGISTRY()
 
   CGaiaCredentialProvider();
-  ~CGaiaCredentialProvider();
+  ~CGaiaCredentialProvider() override;
 
   BEGIN_COM_MAP(CGaiaCredentialProvider)
   COM_INTERFACE_ENTRY(IGaiaCredentialProvider)
@@ -46,6 +58,9 @@
   HRESULT FinalConstruct();
   void FinalRelease();
 
+  // ICredentialUpdateEventsHandler:
+  void UpdateCredentialsIfNeeded(bool user_access_changed) override;
+
   // Returns true if the given usage scenario is supported by GCPW. Currently
   // only CPUS_LOGON and CPUS_UNLOCK_WORKSTATION are supported.
   static bool IsUsageScenarioSupported(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus);
@@ -58,6 +73,81 @@
  private:
   HRESULT DestroyCredentials();
 
+  // Struct to allow passing CComPtr by pointer without the implicit conversion
+  // to ** version of the CComPtr
+  struct ComPtrStorage {
+    ComPtrStorage();
+    ~ComPtrStorage();
+    CComPtr<IGaiaCredential> gaia_cred;
+  };
+
+  // Class used to store state information for the provider that may be accessed
+  // concurrently. This class is thread safe and ensures that the correct state
+  // can be set / queried at any moment. When modifying the state, the modifying
+  // function will return true to state that the operation was completed
+  // successfully to allow the caller to determine if they need to raise a
+  // credential changed event.
+  class ProviderConcurrentState {
+   public:
+    ProviderConcurrentState();
+    ~ProviderConcurrentState();
+
+    // Checks if a user refresh can be performed on the next call to
+    // |GetCredentialCount|. Normally a user refresh is only needed if
+    // |user_access_changed|, but if an auto logon is pending the refresh will
+    // need to be deferred. In this case a pending refresh is queued and
+    // requested when possible on the next call to RequestUserRefreshIfNeeded.
+    // Returns true if a new credential changed event needs to be fired.
+    bool RequestUserRefreshIfNeeded(bool user_access_changed);
+
+    // Sets the credential used to auto logon credential. This function will
+    // always set the credential as auto logon has precedence over a user
+    // refresh. If a user refresh was already requested then it will be changed
+    // to a pending refresh request. Returns true if a new credential changed
+    // event needs to be fired. A credential changed event is not always
+    // required if a previous call to RequestUserRefreshIfNeeded was made that
+    // requested a credential changed event.
+    bool SetAutoLogonCredential(
+        const CComPtr<IGaiaCredential>& auto_logon_credential);
+
+    // Gets the current valid update state of the provider to determnie whether
+    // an auto logon needs to be done or a refresh of the credentials. The two
+    // update states are mutually exclusive. Only one of
+    // |needs_to_refresh_users| and |auto_logon_credential| can be set to a non
+    // default value.
+    void GetUpdatedState(bool* needs_to_refresh_users,
+                         ComPtrStorage* auto_logon_credential);
+
+    // Resets the state of the provider to be default one. On the next call to
+    // GetCredentialCount no auto logon should be performed and no refresh of
+    // credentials should be done.
+    void Reset();
+
+   private:
+    void InternalReset();
+
+    // Reference to the credential that authenticated the user.
+    CComPtr<IGaiaCredential> auto_logon_credential_;
+
+    // Set in NotifyUserAccessDenied to notify the main thread that it will need
+    // to update credentials on the next call to GetCredentialCount. This
+    // ensures that |users_| is only accessed on the main thread. This member
+    // can only be accessed on multiple threads if |token_handle_updater_| is
+    // instantiated, which only occurs between a call to Advise and UnAdvise.
+    bool users_need_to_be_refreshed_ = false;
+
+    // Used to queue up user refresh requests that had to be deferred because
+    // |auto_logon_credential_| has precedence over
+    // |users_need_to_be_refreshed_|.
+    bool pending_users_refresh_needed_ = false;
+
+    // Locks access to |users_need_to_be_refreshed_| and
+    // |auto_logon_credential_| to prevent races between calls to
+    // OnUserAuthenticated / GetCredentialCount and calls to
+    // NotifyUserAccessDenied.
+    base::Lock state_update_lock_;
+  };
+
   // Functions to create credentials during the processing of SetUserArray.
 
   // Creates necessary anonymous credentials given the state of the sign in
@@ -66,15 +156,26 @@
   HRESULT CreateAnonymousCredentialIfNeeded(bool showing_other_user);
 
   // Creates all the reauth credentials from the users that is returned from
-  // |users|. Fills |reauth_sids| with the list of user sids for which a reauth
-  // credential was created.
-  HRESULT CreateReauthCredentials(ICredentialProviderUserArray* users);
+  // |users|. Fills the |gaia_cred| in |auto_logon_credential| with a reference
+  // to the credential that needs to perform auto logon (if any).
+  HRESULT CreateReauthCredentials(ICredentialProviderUserArray* users,
+                                  ComPtrStorage* auto_logon_credential);
 
-  // This function will always add |cred| to |users_| and will also try to
-  // check if the |sid| matches the one set in |set_serialization_sid_| to
-  // allow auto logon of remote connections.
+  // This function will always add |cred| to |users_| and will also try to check
+  // if the |sid| matches the one set in |set_serialization_sid_| to allow auto
+  // logon of remote connections. Fills the |gaia_cred| in
+  // |auto_logon_credential| with a reference to the credential that needs to
+  // perform auto logon (if any).
   void AddCredentialAndCheckAutoLogon(const CComPtr<IGaiaCredential>& cred,
-                                      const base::string16& sid);
+                                      const base::string16& sid,
+                                      ComPtrStorage* auto_logon_credential);
+
+  // Destroys existing credentials and recreates them based on the contents of
+  // |user_array_|. This member must be set via a call to SetUserArray before
+  // RecreateCredentials is called. Fills the |gaia_cred| in
+  // |auto_logon_credential| with a reference to the credential that needs to
+  // perform auto logon (if any).
+  void RecreateCredentials(ComPtrStorage* auto_logon_credential);
 
   void ClearTransient();
   void CleanupOlderVersions();
@@ -113,18 +214,27 @@
   DWORD cpus_flags_ = 0;
   UINT_PTR advise_context_;
   CComPtr<ICredentialProviderEvents> events_;
+  CComPtr<ICredentialProviderUserArray> user_array_;
+
   // List of credentials exposed by this provider.  The first is always the
   // Gaia credential for creating new users.  The rest are reauth credentials.
   std::vector<CComPtr<IGaiaCredential>> users_;
 
-  // SID of the user that was authenticated.
-  CComBSTR new_user_sid_;
+  // Reference to the credential that authenticated the user.
+  CComPtr<IGaiaCredential> auto_logon_credential_;
 
-  // Index in the |users_| array of the credential that performed the
-  // authentication.
-  size_t index_ = std::numeric_limits<size_t>::max();
+  // Background thread updater of token handles that is created on startup to
+  // ensure that user must sign in through gaia if their token handle becomes
+  // invalid while idle on the sign in screen. The updater will also handle the
+  // situation where the machine becomes unenrolled from MDM during sign in.
+  std::unique_ptr<BackgroundTokenHandleUpdater> token_handle_updater_;
 
+  // The SID extracted from the serialization information passed into
+  // SetSerialization. This sid is used to determine which credential to try
+  // to auto logon when GetCredentialCount is called.
   base::string16 set_serialization_sid_;
+
+  ProviderConcurrentState concurrent_state_;
 };
 
 // OBJECT_ENTRY_AUTO() contains an extra semicolon.
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
index 2532902..d4a92c35 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider_unittests.cc
@@ -172,6 +172,230 @@
   ASSERT_EQ(S_OK, provider->UnAdvise());
 }
 
+TEST_F(GcpCredentialProviderTest, AutoLogonAfterUserRefresh) {
+  USES_CONVERSION;
+  CComPtr<ICredentialProvider> provider;
+  ASSERT_EQ(S_OK,
+            CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance(
+                nullptr, IID_ICredentialProvider, (void**)&provider));
+
+  CComPtr<IGaiaCredentialProvider> gaia_provider;
+  ASSERT_EQ(S_OK, provider.QueryInterface(&gaia_provider));
+
+  CComBSTR sid;
+  ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
+                      L"username", L"passowrd", L"Full Name", L"Comment", L"",
+                      L"", &sid));
+  // Start process for logon screen.
+  ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0));
+
+  // Give empty list of users so that only the anonymous credential is created.
+  CComPtr<ICredentialProviderSetUserArray> user_array;
+  ASSERT_EQ(S_OK, provider.QueryInterface(&user_array));
+  FakeCredentialProviderUserArray array;
+  ASSERT_EQ(S_OK, user_array->SetUserArray(&array));
+
+  // Activate the CP.
+  FakeCredentialProviderEvents events;
+  ASSERT_EQ(S_OK, provider->Advise(&events, 0));
+
+  // Only the anonymous credential should exist.
+  DWORD count;
+  DWORD default_index;
+  BOOL autologon;
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
+  EXPECT_FALSE(autologon);
+
+  // Get the anonymous credential.
+  CComPtr<ICredentialProviderCredential> cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &cred));
+
+  // Notify that user access is denied to fake a forced recreation of the users.
+  ICredentialUpdateEventsHandler* update_handler =
+      static_cast<ICredentialUpdateEventsHandler*>(
+          static_cast<CGaiaCredentialProvider*>(provider.p));
+  update_handler->UpdateCredentialsIfNeeded(true);
+
+  // Credential changed event should have been received.
+  EXPECT_TRUE(events.CredentialsChangedReceived());
+  events.ResetCredentialsChangedReceived();
+
+  // At the same time notify that a user has authenticated and requires a
+  // sign in.
+  {
+    // Temporary locker to prevent DCHECKs in OnUserAuthenticated
+    AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_update_locker(
+        AssociatedUserValidator::Get());
+    ASSERT_EQ(S_OK, gaia_provider->OnUserAuthenticated(
+                        cred, CComBSTR(L"username"), CComBSTR(L"password"), sid,
+                        true));
+  }
+
+  // No credential changed should have been signalled here.
+  EXPECT_FALSE(events.CredentialsChangedReceived());
+
+  // GetCredentialCount should return back the same credential that was just
+  // auto logged on.
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(0u, default_index);
+  EXPECT_TRUE(autologon);
+
+  CComPtr<ICredentialProviderCredential> auto_logon_cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &auto_logon_cred));
+  EXPECT_TRUE(auto_logon_cred.IsEqualObject(cred));
+
+  // The next call to GetCredentialCount should return re-created credentials.
+
+  // Fake an update request with no access changes. The pending user refresh
+  // should be queued.
+  update_handler->UpdateCredentialsIfNeeded(false);
+
+  // Credential changed event should have been received.
+  EXPECT_TRUE(events.CredentialsChangedReceived());
+
+  // GetCredentialCount should return new credentials with no auto logon.
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
+  EXPECT_FALSE(autologon);
+
+  CComPtr<ICredentialProviderCredential> new_cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &new_cred));
+  EXPECT_FALSE(new_cred.IsEqualObject(cred));
+
+  // Another request to refresh the credentials should yield no credential
+  // changed event or refresh of credentials.
+  events.ResetCredentialsChangedReceived();
+
+  update_handler->UpdateCredentialsIfNeeded(false);
+
+  // No credential changed event should have been received.
+  EXPECT_FALSE(events.CredentialsChangedReceived());
+
+  // GetCredentialCount should return the same credentials with no change.
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
+  EXPECT_FALSE(autologon);
+
+  CComPtr<ICredentialProviderCredential> unchanged_cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &unchanged_cred));
+  EXPECT_TRUE(new_cred.IsEqualObject(unchanged_cred));
+
+  // Deactivate the CP.
+  ASSERT_EQ(S_OK, provider->UnAdvise());
+}
+
+TEST_F(GcpCredentialProviderTest, AutoLogonBeforeUserRefresh) {
+  USES_CONVERSION;
+  CComPtr<ICredentialProvider> provider;
+  ASSERT_EQ(S_OK,
+            CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance(
+                nullptr, IID_ICredentialProvider, (void**)&provider));
+
+  CComPtr<IGaiaCredentialProvider> gaia_provider;
+  ASSERT_EQ(S_OK, provider.QueryInterface(&gaia_provider));
+
+  CComBSTR sid;
+  ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
+                      L"username", L"passowrd", L"Full Name", L"Comment", L"",
+                      L"", &sid));
+  // Start process for logon screen.
+  ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0));
+
+  // Give empty list of users so that only the anonymous credential is created.
+  CComPtr<ICredentialProviderSetUserArray> user_array;
+  ASSERT_EQ(S_OK, provider.QueryInterface(&user_array));
+  FakeCredentialProviderUserArray array;
+  ASSERT_EQ(S_OK, user_array->SetUserArray(&array));
+
+  // Activate the CP.
+  FakeCredentialProviderEvents events;
+  ASSERT_EQ(S_OK, provider->Advise(&events, 0));
+
+  // Only the anonymous credential should exist.
+  DWORD count;
+  DWORD default_index;
+  BOOL autologon;
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
+  EXPECT_FALSE(autologon);
+
+  // Get the anonymous credential.
+  CComPtr<ICredentialProviderCredential> cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &cred));
+
+  ICredentialUpdateEventsHandler* update_handler =
+      static_cast<ICredentialUpdateEventsHandler*>(
+          static_cast<CGaiaCredentialProvider*>(provider.p));
+
+  // Notify user auto logon first and then notify user access denied to ensure
+  // that auto logon always has precedence over user access denied.
+  {
+    // Temporary locker to prevent DCHECKs in OnUserAuthenticated
+    AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_update_locker(
+        AssociatedUserValidator::Get());
+    ASSERT_EQ(S_OK, gaia_provider->OnUserAuthenticated(
+                        cred, CComBSTR(L"username"), CComBSTR(L"password"), sid,
+                        true));
+  }
+
+  // Credential changed event should have been received.
+  EXPECT_TRUE(events.CredentialsChangedReceived());
+  events.ResetCredentialsChangedReceived();
+
+  // Notify that user access is denied. This should not cause a credential
+  // changed since an event was already processed.
+  update_handler->UpdateCredentialsIfNeeded(true);
+
+  // No credential changed should have been signalled here.
+  EXPECT_FALSE(events.CredentialsChangedReceived());
+
+  // GetCredentialCount should return back the same credential that was just
+  // auto logged on.
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(0u, default_index);
+  EXPECT_TRUE(autologon);
+
+  CComPtr<ICredentialProviderCredential> auto_logon_cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &auto_logon_cred));
+  EXPECT_TRUE(auto_logon_cred.IsEqualObject(cred));
+
+  // The next call to GetCredentialCount should return re-created credentials.
+
+  // Fake an update request with no access changes. The pending user refresh
+  // should be queued.
+  update_handler->UpdateCredentialsIfNeeded(false);
+
+  // Credential changed event should have been received.
+  EXPECT_TRUE(events.CredentialsChangedReceived());
+
+  // GetCredentialCount should return new credentials with no auto logon.
+  ASSERT_EQ(S_OK,
+            provider->GetCredentialCount(&count, &default_index, &autologon));
+  ASSERT_EQ(1u, count);
+  EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
+  EXPECT_FALSE(autologon);
+
+  CComPtr<ICredentialProviderCredential> new_cred;
+  ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &new_cred));
+  EXPECT_FALSE(new_cred.IsEqualObject(cred));
+
+  // Deactivate the CP.
+  ASSERT_EQ(S_OK, provider->UnAdvise());
+}
+
 TEST_F(GcpCredentialProviderTest, AddPersonAfterUserRemove) {
   FakeAssociatedUserValidator associated_user_validator;
   FakeInternetAvailabilityChecker internet_checker;
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.cc b/chrome/credential_provider/gaiacp/os_user_manager.cc
index f6045e56..541f039 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.cc
+++ b/chrome/credential_provider/gaiacp/os_user_manager.cc
@@ -8,8 +8,6 @@
 
 #include <lm.h>
 
-#include <Shellapi.h>  // For <Shlobj.h>
-#include <Shlobj.h>    // For SHFileOperation()
 #include <sddl.h>      // For ConvertSidToStringSid()
 #include <userenv.h>   // For GetUserProfileDirectory()
 #include <wincrypt.h>  // For CryptXXX()
@@ -24,6 +22,7 @@
 #include <memory>
 
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/macros.h"
 #include "base/scoped_native_library.h"
 #include "base/stl_util.h"
@@ -565,9 +564,6 @@
       if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
         LOGFN(ERROR) << "GetUserProfileDirectory hr=" << putHR(hr);
       profiledir[0] = 0;
-    } else {
-      // Double null terminate the profile directory for SHFileOperation().
-      profiledir[length] = 0;
     }
   } else {
     LOGFN(ERROR) << "CreateLogonToken hr=" << putHR(hr);
@@ -579,17 +575,8 @@
     LOGFN(ERROR) << "NetUserDel nsts=" << nsts;
 
   // Force delete the user's profile directory.
-  if (profiledir[0] != 0) {
-    SHFILEOPSTRUCT op;
-    memset(&op, 0, sizeof(op));
-    op.wFunc = FO_DELETE;
-    op.pFrom = profiledir;  // Double null terminated above.
-    op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NO_UI | FOF_SILENT;
-
-    int ret = ::SHFileOperation(&op);
-    if (ret != 0)
-      LOGFN(ERROR) << "SHFileOperation ret=" << ret;
-  }
+  if (*profiledir && !base::DeleteFile(base::FilePath(profiledir), true))
+    LOGFN(ERROR) << "base::DeleteFile";
 
   return S_OK;
 }
diff --git a/chrome/credential_provider/test/com_fakes.h b/chrome/credential_provider/test/com_fakes.h
index 4360c0a..c2dd482 100644
--- a/chrome/credential_provider/test/com_fakes.h
+++ b/chrome/credential_provider/test/com_fakes.h
@@ -85,6 +85,9 @@
   ULONG STDMETHODCALLTYPE Release(void) override;
   IFACEMETHODIMP CredentialsChanged(UINT_PTR upAdviseContext) override;
 
+  bool CredentialsChangedReceived() const { return did_change_; }
+  void ResetCredentialsChangedReceived() { did_change_ = false; }
+
  private:
   bool did_change_ = false;
 };
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index 5f588d72..f26f199 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -599,11 +599,6 @@
   *GetInstanceStorage() = original_validator_;
 }
 
-bool FakeAssociatedUserValidator::IsUserAccessBlocked(
-    const base::string16& sid) const {
-  return locked_user_sids_.find(sid) != locked_user_sids_.end();
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 FakeInternetAvailabilityChecker::FakeInternetAvailabilityChecker(
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index 2068364..d471c05c 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -294,9 +294,7 @@
   explicit FakeAssociatedUserValidator(base::TimeDelta validation_timeout);
   ~FakeAssociatedUserValidator() override;
 
-  // Returns whether the user should be locked out of sign in (only used in
-  // tests).
-  bool IsUserAccessBlocked(const base::string16& sid) const;
+  using AssociatedUserValidator::IsUserAccessBlocked;
 
  private:
   AssociatedUserValidator* original_validator_ = nullptr;
diff --git a/chrome/installer/util/BUILD.gn b/chrome/installer/util/BUILD.gn
index 6c6c3f5..a3a803e 100644
--- a/chrome/installer/util/BUILD.gn
+++ b/chrome/installer/util/BUILD.gn
@@ -210,7 +210,7 @@
   ]
   extractor_datafile =
       "//chrome/installer/util/prebuild/create_installer_string_rc.py"
-  
+
   grdfile_folder = "//chrome/app/"
   if (is_chrome_branded) {
     grdfile_name = "google_chrome_strings"
@@ -218,14 +218,12 @@
     grdfile_name = "chromium_strings"
   }
   xtb_relative_path = "resources"
-  grd_files_info = [
-    [
-      grdfile_folder,
-      grdfile_name,
-      xtb_relative_path,
-      default_embedded_i18_locales
-    ]
-  ]
+  grd_files_info = [ [
+        grdfile_folder,
+        grdfile_name,
+        xtb_relative_path,
+        default_embedded_i18_locales,
+      ] ]
 
   output_file_name_base = "installer_util_strings"
 
@@ -274,8 +272,6 @@
       "experiment_unittest.cc",
       "google_update_settings_unittest.cc",
       "install_util_unittest.cc",
-      "installer_util_test_common.cc",
-      "installer_util_test_common.h",
       "l10n_string_util_unittest.cc",
       "logging_installer_unittest.cc",
       "lzma_file_allocator_unittest.cc",
diff --git a/chrome/installer/util/duplicate_tree_detector_unittest.cc b/chrome/installer/util/duplicate_tree_detector_unittest.cc
index 7f1c909..bb34fec 100644
--- a/chrome/installer/util/duplicate_tree_detector_unittest.cc
+++ b/chrome/installer/util/duplicate_tree_detector_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
 #include "chrome/installer/util/duplicate_tree_detector.h"
-#include "chrome/installer/util/installer_util_test_common.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -57,7 +56,7 @@
     CreateTextFile(f2.MaybeAsASCII(), text_content_2_);
     ASSERT_TRUE(base::PathExists(f2));
 
-    ASSERT_TRUE(installer::test::CopyFileHierarchy(d1, second_root));
+    ASSERT_TRUE(base::CopyDirectory(d1, second_root, true));
   }
 
   base::ScopedTempDir temp_source_dir_;
@@ -148,7 +147,7 @@
   // This file should be the same.
   base::FilePath dest_file(temp_dest_dir_.GetPath());
   dest_file = dest_file.AppendASCII("F1");
-  ASSERT_TRUE(installer::test::CopyFileHierarchy(source_file, dest_file));
+  ASSERT_TRUE(base::CopyFile(source_file, dest_file));
 
   // This file should be different.
   base::FilePath other_file(temp_dest_dir_.GetPath());
diff --git a/chrome/installer/util/installer_util_test_common.cc b/chrome/installer/util/installer_util_test_common.cc
deleted file mode 100644
index 6c15537..0000000
--- a/chrome/installer/util/installer_util_test_common.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2011 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/installer/util/installer_util_test_common.h"
-
-#include <windows.h>
-#include <shellapi.h>
-
-#include "base/files/file_path.h"
-#include "base/strings/string16.h"
-
-namespace installer {
-
-namespace test {
-
-bool CopyFileHierarchy(const base::FilePath& from, const base::FilePath& to) {
-  // In SHFILEOPSTRUCT below, |pFrom| and |pTo| have to be double-null
-  // terminated: http://msdn.microsoft.com/library/bb759795.aspx
-  base::string16 double_null_from(from.value());
-  double_null_from.push_back(L'\0');
-  base::string16 double_null_to(to.value());
-  double_null_to.push_back(L'\0');
-
-  SHFILEOPSTRUCT file_op = {};
-  file_op.wFunc = FO_COPY;
-  file_op.pFrom = double_null_from.c_str();
-  file_op.pTo = double_null_to.c_str();
-  file_op.fFlags = FOF_NO_UI;
-
-  return (SHFileOperation(&file_op) == 0 && !file_op.fAnyOperationsAborted);
-}
-
-}  // namespace test
-
-}  // namespace installer
diff --git a/chrome/installer/util/installer_util_test_common.h b/chrome/installer/util/installer_util_test_common.h
deleted file mode 100644
index 0a40968..0000000
--- a/chrome/installer/util/installer_util_test_common.h
+++ /dev/null
@@ -1,24 +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.
-
-#ifndef CHROME_INSTALLER_UTIL_INSTALLER_UTIL_TEST_COMMON_H_
-#define CHROME_INSTALLER_UTIL_INSTALLER_UTIL_TEST_COMMON_H_
-
-namespace base {
-class FilePath;
-}
-
-namespace installer {
-
-namespace test {
-
-// Copies the hierarcy in |from| to |to|.
-// Keeps all file properties identical (creation time, etc.).
-bool CopyFileHierarchy(const base::FilePath& from, const base::FilePath& to);
-
-}  // namespace test
-
-}  // namespace installer
-
-#endif  // CHROME_INSTALLER_UTIL_INSTALLER_UTIL_TEST_COMMON_H_
diff --git a/chrome/installer/util/move_tree_work_item_unittest.cc b/chrome/installer/util/move_tree_work_item_unittest.cc
index f5a5e6c2..952be9f 100644
--- a/chrome/installer/util/move_tree_work_item_unittest.cc
+++ b/chrome/installer/util/move_tree_work_item_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chrome/installer/util/installer_util_test_common.h"
 #include "chrome/installer/util/work_item.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -388,10 +387,10 @@
   CreateTextFile(from_file.value(), kTextContent1);
   ASSERT_TRUE(base::PathExists(from_file));
 
-  // // Create a file hierarchy identical to the one in the source directory.
+  // Create a file hierarchy identical to the one in the source directory.
   base::FilePath to_dir(temp_from_dir_.GetPath());
   to_dir = to_dir.AppendASCII("To_Dir");
-  ASSERT_TRUE(installer::test::CopyFileHierarchy(from_dir1, to_dir));
+  ASSERT_TRUE(base::CopyDirectory(from_dir1, to_dir, true));
 
   // Lock one of the files in the to destination directory to prevent moves.
   base::FilePath orig_to_file(
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index 4da93d2f..4ca1e0a 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -55,11 +55,16 @@
 // Returns an array with the RGBA color components.
 v8::Local<v8::Value> RGBAColorToArray(v8::Isolate* isolate,
                                       const RGBAColor& color) {
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Array> color_array = v8::Array::New(isolate, 4);
-  color_array->Set(0, v8::Int32::New(isolate, color.r));
-  color_array->Set(1, v8::Int32::New(isolate, color.g));
-  color_array->Set(2, v8::Int32::New(isolate, color.b));
-  color_array->Set(3, v8::Int32::New(isolate, color.a));
+  color_array->CreateDataProperty(context, 0, v8::Int32::New(isolate, color.r))
+      .Check();
+  color_array->CreateDataProperty(context, 1, v8::Int32::New(isolate, color.g))
+      .Check();
+  color_array->CreateDataProperty(context, 2, v8::Int32::New(isolate, color.b))
+      .Check();
+  color_array->CreateDataProperty(context, 3, v8::Int32::New(isolate, color.a))
+      .Check();
   return color_array;
 }
 
@@ -725,12 +730,17 @@
 
   std::vector<InstantMostVisitedItemIDPair> instant_mv_items;
   search_box->GetMostVisitedItems(&instant_mv_items);
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
   v8::Local<v8::Object> v8_mv_items =
       v8::Array::New(isolate, instant_mv_items.size());
   for (size_t i = 0; i < instant_mv_items.size(); ++i) {
     InstantRestrictedID rid = instant_mv_items[i].first;
-    v8_mv_items->Set(i, GenerateMostVisitedItem(isolate, device_pixel_ratio,
-                                                render_view_id, rid));
+    v8_mv_items
+        ->CreateDataProperty(
+            context, i,
+            GenerateMostVisitedItem(isolate, device_pixel_ratio, render_view_id,
+                                    rid))
+        .Check();
   }
   return v8_mv_items;
 }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 81cdc9ea..4cb4e01 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1062,7 +1062,7 @@
       "../browser/ui/webui/prefs_internals_browsertest.cc",
       "../browser/ui/webui/profile_helper_browsertest.cc",
       "../browser/ui/webui/set_as_default_browser_ui_browsertest_win.cc",
-      "../browser/ui/webui/settings/md_settings_ui_browsertest.cc",
+      "../browser/ui/webui/settings/settings_ui_browsertest.cc",
       "../browser/ui/webui/signin/user_manager_ui_browsertest.cc",
       "../browser/ui/webui/webui_browsertest.cc",
       "../browser/ui/webui/webui_webview_browsertest.cc",
@@ -1859,11 +1859,13 @@
         "../browser/chromeos/login/oobe_browsertest.cc",
         "../browser/chromeos/login/oobe_interactive_ui_test.cc",
         "../browser/chromeos/login/oobe_localization_browsertest.cc",
+        "../browser/chromeos/login/password_change_browsertest.cc",
         "../browser/chromeos/login/proxy_auth_dialog_browsertest.cc",
         "../browser/chromeos/login/quick_unlock/pin_migration_browsertest.cc",
         "../browser/chromeos/login/reset_browsertest.cc",
         "../browser/chromeos/login/saml/saml_browsertest.cc",
         "../browser/chromeos/login/screens/app_downloading_screen_browsertest.cc",
+        "../browser/chromeos/login/screens/fingerprint_setup_browsertest.cc",
         "../browser/chromeos/login/screens/hid_detection_screen_browsertest.cc",
         "../browser/chromeos/login/screens/mock_arc_terms_of_service_screen.cc",
         "../browser/chromeos/login/screens/mock_arc_terms_of_service_screen.h",
@@ -1885,6 +1887,8 @@
         "../browser/chromeos/login/session_login_browsertest.cc",
         "../browser/chromeos/login/signin/device_id_browsertest.cc",
         "../browser/chromeos/login/signin/oauth2_browsertest.cc",
+        "../browser/chromeos/login/test/active_directory_login_mixin.cc",
+        "../browser/chromeos/login/test/active_directory_login_mixin.h",
         "../browser/chromeos/login/test/enrollment_helper_mixin.cc",
         "../browser/chromeos/login/test/enrollment_helper_mixin.h",
         "../browser/chromeos/login/test/enrollment_ui_mixin.cc",
@@ -1897,6 +1901,8 @@
         "../browser/chromeos/login/test/https_forwarder.h",
         "../browser/chromeos/login/test/local_policy_test_server_mixin.cc",
         "../browser/chromeos/login/test/local_policy_test_server_mixin.h",
+        "../browser/chromeos/login/test/login_manager_mixin.cc",
+        "../browser/chromeos/login/test/login_manager_mixin.h",
         "../browser/chromeos/login/test/login_screen_tester.cc",
         "../browser/chromeos/login/test/login_screen_tester.h",
         "../browser/chromeos/login/test/network_portal_detector_mixin.cc",
@@ -1930,7 +1936,6 @@
         "../browser/chromeos/policy/device_policy_cros_browser_test.cc",
         "../browser/chromeos/policy/device_policy_cros_browser_test.h",
         "../browser/chromeos/policy/device_quirks_policy_browsertest.cc",
-        "../browser/chromeos/policy/device_status_collector_browsertest.cc",
         "../browser/chromeos/policy/device_system_use_24hour_clock_browsertest.cc",
         "../browser/chromeos/policy/display_resolution_handler_browsertest.cc",
         "../browser/chromeos/policy/display_rotation_default_handler_browsertest.cc",
@@ -1942,6 +1947,7 @@
         "../browser/chromeos/policy/restore_on_startup_browsertest_chromeos.cc",
         "../browser/chromeos/policy/signin_profile_apps_policy_browsertest.cc",
         "../browser/chromeos/policy/site_isolation_flag_handling_browsertest.cc",
+        "../browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc",
         "../browser/chromeos/policy/unaffiliated_arc_allowed_browsertest.cc",
         "../browser/chromeos/policy/user_affiliation_browsertest.cc",
         "../browser/chromeos/policy/user_cloud_external_data_manager_browsertest.cc",
@@ -2773,6 +2779,7 @@
     "../browser/password_manager/password_store_mac_unittest.cc",
     "../browser/password_manager/password_store_x_unittest.cc",
     "../browser/payments/payment_handler_permission_context_unittest.cc",
+    "../browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc",
     "../browser/performance_manager/decorators/page_almost_idle_decorator_unittest.cc",
     "../browser/performance_manager/graph/frame_node_impl_unittest.cc",
     "../browser/performance_manager/graph/graph_test_harness.cc",
diff --git a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/end.js b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/end.js
new file mode 100644
index 0000000..acef814
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/end.js
@@ -0,0 +1,10 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+if (typeof hasRunContentScriptAtDocumentStart == 'undefined') {
+  chrome.extension.sendRequest('document_start script has not run!');
+} else if (window.parent !== window) {
+  // Assume iframe
+  chrome.extension.sendRequest('jsresult/' + document.body.textContent.trim());
+}
diff --git a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/manifest.json
index 4bbfe99..5f33dcd9 100644
--- a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/manifest.json
+++ b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/manifest.json
@@ -11,6 +11,7 @@
     {
       "run_at": "document_end",
       "matches": ["http://*/*"],
+      "exclude_matches": ["http://*/*test_file_with_javascript_url*"],
       "js": ["content_script.js"],
       "all_frames": true
     },
@@ -20,6 +21,20 @@
       "match_about_blank": true,
       "js": ["content_script2.js"],
       "all_frames": true
+    },
+    {
+      "run_at": "document_start",
+      "matches": ["http://*/*test_file_with_javascript_url*"],
+      "match_about_blank": true,
+      "js": ["start.js"],
+      "all_frames": true
+    },
+    {
+      "run_at": "document_end",
+      "matches": ["http://*/*test_file_with_javascript_url*"],
+      "match_about_blank": true,
+      "js": ["end.js"],
+      "all_frames": true
     }
   ]
 }
diff --git a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/start.js b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/start.js
new file mode 100644
index 0000000..d15c38a9
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/start.js
@@ -0,0 +1,6 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This variable will be checked in end.js
+var hasRunContentScriptAtDocumentStart = true;
diff --git a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/test.js b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/test.js
index 832e4ea..099b0b5 100644
--- a/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/test.js
+++ b/chrome/test/data/extensions/api_test/content_scripts/about_blank_iframes/test.js
@@ -43,7 +43,13 @@
       chrome.tabs.create({ url: test_url });
     },
     function testDocumentStartRunsInSameWorldAsDocumentEndOfJavaScriptUrl() {
-      chrome.test.listenOnce(onRequest, checkFirstMessageEquals('parent'));
+      onRequest.addListener(function listener(request) {
+        onRequest.removeListener(listener);
+        // The empty document was replaced with the result of the evaluated
+        // JavaScript code.
+        checkFirstMessageEquals('jsresult/something')(request);
+        chrome.test.succeed();
+      });
       chrome.test.log('Creating tab...');
       var test_url =
           ('http://localhost:PORT/extensions/' +
diff --git a/chrome/test/data/webui/extensions/activity_log_history_test.js b/chrome/test/data/webui/extensions/activity_log_history_test.js
index 51746a38..5a06f5c 100644
--- a/chrome/test/data/webui/extensions/activity_log_history_test.js
+++ b/chrome/test/data/webui/extensions/activity_log_history_test.js
@@ -329,7 +329,7 @@
 
           expectEquals(activityLogItems.length, 3);
           proxyDelegate.resetResolver('getExtensionActivityLog');
-          activityLogItems[0].$$('#activity-delete-button').click();
+          activityLogItems[0].$$('#activity-delete').click();
 
           // We delete the first item so we should only have one item left. This
           // chaining reflects the API calls made from activity_log.js.
diff --git a/chrome/test/data/webui/extensions/error_page_test.js b/chrome/test/data/webui/extensions/error_page_test.js
index 7f1eaef..0f53afc 100644
--- a/chrome/test/data/webui/extensions/error_page_test.js
+++ b/chrome/test/data/webui/extensions/error_page_test.js
@@ -118,7 +118,7 @@
       expectTrue(error.querySelector('iron-icon').icon == 'cr:warning');
 
       mockDelegate.testClickingCalls(
-          error.querySelector('.icon-delete-gray button'), 'deleteErrors',
+          error.querySelector('.icon-delete-gray'), 'deleteErrors',
           [extensionId, [manifestError.id]]);
     });
 
diff --git a/chrome/test/data/webui/extensions/kiosk_mode_test.js b/chrome/test/data/webui/extensions/kiosk_mode_test.js
index 24cefac..b7a634cc5 100644
--- a/chrome/test/data/webui/extensions/kiosk_mode_test.js
+++ b/chrome/test/data/webui/extensions/kiosk_mode_test.js
@@ -106,8 +106,7 @@
             // disabled.
             expectTrue(dialog.$$('cr-checkbox').hidden);
 
-            MockInteractions.tap(
-                items[0].querySelector('.icon-delete-gray button'));
+            MockInteractions.tap(items[0].querySelector('.icon-delete-gray'));
             Polymer.dom.flush();
             return browserProxy.whenCalled('removeKioskApp');
           })
diff --git a/chrome/test/data/webui/polymer_browser_test_base.js b/chrome/test/data/webui/polymer_browser_test_base.js
index 95b2baa..9569f2df 100644
--- a/chrome/test/data/webui/polymer_browser_test_base.js
+++ b/chrome/test/data/webui/polymer_browser_test_base.js
@@ -86,13 +86,6 @@
         throw e;
       }
     };
-
-    // Import Polymer before running tests.
-    suiteSetup(function() {
-      if (!window.Polymer) {
-        return PolymerTest.importHtml('chrome://resources/html/polymer.html');
-      }
-    });
   },
 
   /** @override */
diff --git a/chromeos/dbus/cups_proxy/BUILD.gn b/chromeos/dbus/cups_proxy/BUILD.gn
new file mode 100644
index 0000000..2561595
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
+
+component("cups_proxy") {
+  defines = [ "IS_CUPS_PROXY_IMPL" ]
+
+  deps = [
+    "//base",
+    "//dbus",
+  ]
+
+  sources = [
+    "cups_proxy_client.cc",
+    "cups_proxy_client.h",
+    "fake_cups_proxy_client.cc",
+    "fake_cups_proxy_client.h",
+  ]
+}
diff --git a/chromeos/dbus/cups_proxy/OWNERS b/chromeos/dbus/cups_proxy/OWNERS
new file mode 100644
index 0000000..5a872c7
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/OWNERS
@@ -0,0 +1,2 @@
+luum@chromium.org
+skau@chromium.org
diff --git a/chromeos/dbus/cups_proxy/cups_proxy_client.cc b/chromeos/dbus/cups_proxy/cups_proxy_client.cc
new file mode 100644
index 0000000..7287262
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/cups_proxy_client.cc
@@ -0,0 +1,101 @@
+// 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 "chromeos/dbus/cups_proxy/cups_proxy_client.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/dbus/cups_proxy/fake_cups_proxy_client.h"
+#include "dbus/bus.h"
+#include "dbus/message.h"
+#include "dbus/object_proxy.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+
+namespace {
+
+CupsProxyClient* g_instance = nullptr;
+
+class CupsProxyClientImpl : public CupsProxyClient {
+ public:
+  CupsProxyClientImpl() = default;
+  ~CupsProxyClientImpl() override = default;
+
+  // CupsProxyClient override.
+  void BootstrapMojoConnection(
+      base::ScopedFD fd,
+      base::OnceCallback<void(bool success)> result_callback) override {
+    dbus::MethodCall method_call(::printing::kCupsProxyDaemonInterface,
+                                 ::printing::kBootstrapMojoConnectionMethod);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendFileDescriptor(fd.get());
+    daemon_proxy_->CallMethod(
+        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+        base::BindOnce(&CupsProxyClientImpl::OnBootstrapMojoConnectionResponse,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       std::move(result_callback)));
+  }
+
+  void Init(dbus::Bus* const bus) {
+    daemon_proxy_ =
+        bus->GetObjectProxy(::printing::kCupsProxyDaemonName,
+                            dbus::ObjectPath(::printing::kCupsProxyDaemonPath));
+  }
+
+ private:
+  dbus::ObjectProxy* daemon_proxy_ = nullptr;
+
+  // Passes the success/failure of |dbus_response| on to |result_callback|.
+  void OnBootstrapMojoConnectionResponse(
+      base::OnceCallback<void(bool success)> result_callback,
+      dbus::Response* const dbus_response) {
+    const bool success = dbus_response != nullptr;
+    std::move(result_callback).Run(success);
+  }
+
+  // Must be last class member.
+  base::WeakPtrFactory<CupsProxyClientImpl> weak_ptr_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(CupsProxyClientImpl);
+};
+
+}  // namespace
+
+CupsProxyClient::CupsProxyClient() {
+  DCHECK(!g_instance);
+  g_instance = this;
+}
+
+CupsProxyClient::~CupsProxyClient() {
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
+}
+
+// static
+void CupsProxyClient::Initialize(dbus::Bus* bus) {
+  DCHECK(bus);
+  (new CupsProxyClientImpl())->Init(bus);
+}
+
+// static
+void CupsProxyClient::InitializeFake() {
+  new FakeCupsProxyClient();
+}
+
+// static
+void CupsProxyClient::Shutdown() {
+  DCHECK(g_instance);
+  delete g_instance;
+}
+
+// static
+CupsProxyClient* CupsProxyClient::Get() {
+  return g_instance;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/cups_proxy/cups_proxy_client.h b/chromeos/dbus/cups_proxy/cups_proxy_client.h
new file mode 100644
index 0000000..85115897
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/cups_proxy_client.h
@@ -0,0 +1,60 @@
+// 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 CHROMEOS_DBUS_CUPS_PROXY_CUPS_PROXY_CLIENT_H_
+#define CHROMEOS_DBUS_CUPS_PROXY_CUPS_PROXY_CLIENT_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/component_export.h"
+#include "base/files/scoped_file.h"
+
+namespace dbus {
+class Bus;
+}  // namespace dbus
+
+namespace chromeos {
+
+// D-Bus client for the CupsProxyDaemon.
+//
+// Sole method bootstraps a Mojo connection between the daemon and Chrome,
+// allowing Chrome to serve printing requests proxied by the daemon.
+class COMPONENT_EXPORT(CUPS_PROXY) CupsProxyClient {
+ public:
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates and initializes a fake global instance if not already created.
+  static void InitializeFake();
+
+  // Destroys the global instance.
+  static void Shutdown();
+
+  // Returns the global instance which may be null if not initialized.
+  static CupsProxyClient* Get();
+
+  // Passes the file descriptor |fd| over D-Bus to the CupsProxyDaemon.
+  // * The daemon expects a Mojo invitation in |fd| with an attached Mojo pipe.
+  // * The daemon will bind the Mojo pipe to an
+  //   chromeos::printing::mojom::CupsProxierPtr.
+  // * Upon completion of the D-Bus call, |result_callback| will be invoked to
+  //   indicate success or failure.
+  // * This method will first wait for the CupsProxyService to become available.
+  virtual void BootstrapMojoConnection(
+      base::ScopedFD fd,
+      base::OnceCallback<void(bool success)> result_callback) = 0;
+
+ protected:
+  // Initialize/Shutdown should be used instead.
+  CupsProxyClient();
+  virtual ~CupsProxyClient();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CupsProxyClient);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_CUPS_PROXY_CUPS_PROXY_CLIENT_H_
diff --git a/chromeos/dbus/cups_proxy/fake_cups_proxy_client.cc b/chromeos/dbus/cups_proxy/fake_cups_proxy_client.cc
new file mode 100644
index 0000000..105d82d3
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/fake_cups_proxy_client.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/dbus/cups_proxy/fake_cups_proxy_client.h"
+
+#include <utility>
+
+#include "base/callback.h"
+
+namespace chromeos {
+
+FakeCupsProxyClient::FakeCupsProxyClient() = default;
+FakeCupsProxyClient::~FakeCupsProxyClient() = default;
+
+void FakeCupsProxyClient::BootstrapMojoConnection(
+    base::ScopedFD fd,
+    base::OnceCallback<void(bool success)> result_callback) {
+  const bool success = true;
+  std::move(result_callback).Run(success);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/dbus/cups_proxy/fake_cups_proxy_client.h b/chromeos/dbus/cups_proxy/fake_cups_proxy_client.h
new file mode 100644
index 0000000..dbb5589
--- /dev/null
+++ b/chromeos/dbus/cups_proxy/fake_cups_proxy_client.h
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_DBUS_CUPS_PROXY_FAKE_CUPS_PROXY_CLIENT_H_
+#define CHROMEOS_DBUS_CUPS_PROXY_FAKE_CUPS_PROXY_CLIENT_H_
+
+#include "base/callback_forward.h"
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "chromeos/dbus/cups_proxy/cups_proxy_client.h"
+
+namespace chromeos {
+
+// Fake implementation of CupsProxyClient. This is currently a no-op fake.
+class FakeCupsProxyClient : public CupsProxyClient {
+ public:
+  FakeCupsProxyClient();
+  ~FakeCupsProxyClient() override;
+
+  // CupsProxyClient override.
+  void BootstrapMojoConnection(
+      base::ScopedFD fd,
+      base::OnceCallback<void(bool success)> result_callback) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeCupsProxyClient);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_DBUS_CUPS_PROXY_FAKE_CUPS_PROXY_CLIENT_H_
diff --git a/chromeos/login/auth/BUILD.gn b/chromeos/login/auth/BUILD.gn
index b02ac08c..cf994f3 100644
--- a/chromeos/login/auth/BUILD.gn
+++ b/chromeos/login/auth/BUILD.gn
@@ -59,6 +59,8 @@
     "login_performer.h",
     "stub_authenticator.cc",
     "stub_authenticator.h",
+    "stub_authenticator_builder.cc",
+    "stub_authenticator_builder.h",
     "test_attempt_state.cc",
     "test_attempt_state.h",
     "user_context.cc",
diff --git a/chromeos/login/auth/stub_authenticator.cc b/chromeos/login/auth/stub_authenticator.cc
index 338b3cc..cd94f9f 100644
--- a/chromeos/login/auth/stub_authenticator.cc
+++ b/chromeos/login/auth/stub_authenticator.cc
@@ -40,8 +40,17 @@
   // during non-online re-auth |user_context| does not have a gaia id.
   if (expected_user_context_.GetAccountId() == user_context.GetAccountId() &&
       *expected_user_context_.GetKey() == *user_context.GetKey()) {
-    task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&StubAuthenticator::OnAuthSuccess, this));
+    switch (auth_action_) {
+      case AuthAction::kAuthSuccess:
+        task_runner_->PostTask(
+            FROM_HERE, base::BindOnce(&StubAuthenticator::OnAuthSuccess, this));
+        break;
+      case AuthAction::kPasswordChange:
+        task_runner_->PostTask(
+            FROM_HERE,
+            base::BindOnce(&StubAuthenticator::OnPasswordChangeDetected, this));
+        break;
+    }
     return;
   }
   GoogleServiceAuthError error =
@@ -111,6 +120,7 @@
       expected_user_context_.GetAccountId().GetUserEmail() + kUserIdHashSuffix);
   user_context.GetKey()->Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
                                    "some-salt");
+
   consumer_->OnAuthSuccess(user_context);
 }
 
@@ -119,9 +129,26 @@
 }
 
 void StubAuthenticator::RecoverEncryptedData(const std::string& old_password) {
+  if (old_password_ != old_password) {
+    if (data_recovery_notifier_)
+      data_recovery_notifier_.Run(DataRecoveryStatus::kRecoveryFailed);
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&StubAuthenticator::OnPasswordChangeDetected, this));
+    return;
+  }
+
+  if (data_recovery_notifier_)
+    data_recovery_notifier_.Run(DataRecoveryStatus::kRecovered);
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&StubAuthenticator::OnAuthSuccess, this));
 }
 
 void StubAuthenticator::ResyncEncryptedData() {
+  if (data_recovery_notifier_)
+    data_recovery_notifier_.Run(DataRecoveryStatus::kResynced);
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&StubAuthenticator::OnAuthSuccess, this));
 }
 
 void StubAuthenticator::SetExpectedCredentials(
@@ -131,4 +158,8 @@
 
 StubAuthenticator::~StubAuthenticator() = default;
 
+void StubAuthenticator::OnPasswordChangeDetected() {
+  consumer_->OnPasswordChangeDetected();
+}
+
 }  // namespace chromeos
diff --git a/chromeos/login/auth/stub_authenticator.h b/chromeos/login/auth/stub_authenticator.h
index 7c4e48ed2..08bc0c5c 100644
--- a/chromeos/login/auth/stub_authenticator.h
+++ b/chromeos/login/auth/stub_authenticator.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
@@ -22,10 +23,20 @@
 namespace chromeos {
 
 class AuthStatusConsumer;
+class StubAuthenticatorBuilder;
 
 class COMPONENT_EXPORT(CHROMEOS_LOGIN_AUTH) StubAuthenticator
     : public Authenticator {
  public:
+  enum class DataRecoveryStatus {
+    kNone,
+    kRecovered,
+    kRecoveryFailed,
+    kResynced
+  };
+  using DataRecoveryNotifier =
+      base::RepeatingCallback<void(DataRecoveryStatus status)>;
+
   StubAuthenticator(AuthStatusConsumer* consumer,
                     const UserContext& expected_user_context);
 
@@ -46,15 +57,31 @@
   void RecoverEncryptedData(const std::string& old_password) override;
   void ResyncEncryptedData() override;
 
-  virtual void SetExpectedCredentials(const UserContext& user_context);
+  void SetExpectedCredentials(const UserContext& user_context);
 
  protected:
   ~StubAuthenticator() override;
 
  private:
+  friend class StubAuthenticatorBuilder;
+
+  enum class AuthAction { kAuthSuccess, kPasswordChange };
+
+  void OnPasswordChangeDetected();
+
   UserContext expected_user_context_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
+  // The action taken for login requests that match the expected context.
+  AuthAction auth_action_ = AuthAction::kAuthSuccess;
+
+  // For password change requests - the old user password.
+  std::string old_password_;
+
+  // If set, the callback that will be called as authenticator handles user
+  // encrypted data recovery during password change flow.
+  DataRecoveryNotifier data_recovery_notifier_;
+
   DISALLOW_COPY_AND_ASSIGN(StubAuthenticator);
 };
 
diff --git a/chromeos/login/auth/stub_authenticator_builder.cc b/chromeos/login/auth/stub_authenticator_builder.cc
new file mode 100644
index 0000000..287beed
--- /dev/null
+++ b/chromeos/login/auth/stub_authenticator_builder.cc
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/login/auth/stub_authenticator_builder.h"
+
+namespace chromeos {
+
+StubAuthenticatorBuilder::StubAuthenticatorBuilder(
+    const UserContext& expected_user_context)
+    : expected_user_context_(expected_user_context) {}
+
+StubAuthenticatorBuilder::~StubAuthenticatorBuilder() = default;
+
+scoped_refptr<Authenticator> StubAuthenticatorBuilder::Create(
+    AuthStatusConsumer* consumer) {
+  scoped_refptr<StubAuthenticator> authenticator =
+      new StubAuthenticator(consumer, expected_user_context_);
+  authenticator->auth_action_ = auth_action_;
+  if (auth_action_ == StubAuthenticator::AuthAction::kPasswordChange)
+    authenticator->old_password_ = old_password_;
+  if (data_recovery_notifier_)
+    authenticator->data_recovery_notifier_ = data_recovery_notifier_;
+  return authenticator;
+}
+
+void StubAuthenticatorBuilder::SetUpPasswordChange(
+    const std::string& old_password,
+    const StubAuthenticator::DataRecoveryNotifier& notifier) {
+  auth_action_ = StubAuthenticator::AuthAction::kPasswordChange;
+  old_password_ = old_password;
+  data_recovery_notifier_ = notifier;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/login/auth/stub_authenticator_builder.h b/chromeos/login/auth/stub_authenticator_builder.h
new file mode 100644
index 0000000..198fe6f
--- /dev/null
+++ b/chromeos/login/auth/stub_authenticator_builder.h
@@ -0,0 +1,56 @@
+// 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 CHROMEOS_LOGIN_AUTH_STUB_AUTHENTICATOR_BUILDER_H_
+#define CHROMEOS_LOGIN_AUTH_STUB_AUTHENTICATOR_BUILDER_H_
+
+#include <string>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "chromeos/login/auth/stub_authenticator.h"
+#include "chromeos/login/auth/user_context.h"
+
+namespace chromeos {
+
+// Helper class for creating a StubAuthenticator with certain configuration.
+// Useful in tests for injecting StubAuthenticators to be used during user
+// login.
+class COMPONENT_EXPORT(CHROMEOS_LOGIN_AUTH) StubAuthenticatorBuilder {
+ public:
+  explicit StubAuthenticatorBuilder(const UserContext& expected_user_context);
+  ~StubAuthenticatorBuilder();
+
+  scoped_refptr<Authenticator> Create(AuthStatusConsumer* consumer);
+
+  // Sets up the stub Authenticator to report password changed.
+  // |old_password| - the expected old user password. The authenticator will use
+  //   it to handle data migration requests (it will report failure if the
+  //   provided old password does not match this one).
+  // |notifier| - can be empty. If set called when a user data recovery is
+  //     attempted.
+  void SetUpPasswordChange(
+      const std::string& old_password,
+      const StubAuthenticator::DataRecoveryNotifier& notifier);
+
+ private:
+  const UserContext expected_user_context_;
+
+  // Action to be performed on successful auth against expected user context..
+  StubAuthenticator::AuthAction auth_action_ =
+      StubAuthenticator::AuthAction::kAuthSuccess;
+
+  // For kPasswordChange action, the old user password.
+  std::string old_password_;
+  // For kPasswordChange action, the callback to be called to report user data
+  // recovery result.
+  StubAuthenticator::DataRecoveryNotifier data_recovery_notifier_;
+
+  DISALLOW_COPY_AND_ASSIGN(StubAuthenticatorBuilder);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_LOGIN_AUTH_STUB_AUTHENTICATOR_BUILDER_H_
diff --git a/chromeos/services/assistant/assistant_settings_manager_impl.cc b/chromeos/services/assistant/assistant_settings_manager_impl.cc
index 41d3ec41..555e1fe8 100644
--- a/chromeos/services/assistant/assistant_settings_manager_impl.cc
+++ b/chromeos/services/assistant/assistant_settings_manager_impl.cc
@@ -167,8 +167,25 @@
 void AssistantSettingsManagerImpl::SyncSpeakerIdEnrollmentStatus() {
   DCHECK(service_->main_task_runner()->RunsTasksInCurrentSequence());
 
-  // Disable status check on M74 since we do not have the API available.
-  return;
+  if (service_->assistant_state()->allowed_state() !=
+          ash::mojom::AssistantAllowedState::ALLOWED ||
+      !base::FeatureList::IsEnabled(
+          assistant::features::kAssistantVoiceMatch)) {
+    return;
+  }
+
+  assistant_manager_service_->assistant_manager_internal()
+      ->GetSpeakerIdEnrollmentStatus(
+          kUserID,
+          [weak_ptr = weak_factory_.GetWeakPtr(),
+           task_runner = service_->main_task_runner()](
+              const assistant_client::SpeakerIdEnrollmentStatus& status) {
+            task_runner->PostTask(
+                FROM_HERE,
+                base::BindOnce(&AssistantSettingsManagerImpl::
+                                   HandleSpeakerIdEnrollmentStatusSync,
+                               weak_ptr, status));
+          });
 }
 
 void AssistantSettingsManagerImpl::HandleSpeakerIdEnrollmentUpdate(
@@ -200,6 +217,24 @@
   }
 }
 
+void AssistantSettingsManagerImpl::HandleSpeakerIdEnrollmentStatusSync(
+    const assistant_client::SpeakerIdEnrollmentStatus& status) {
+  DCHECK(service_->main_task_runner()->RunsTasksInCurrentSequence());
+
+  speaker_id_enrollment_done_ = status.user_model_exists;
+
+  if (speaker_id_enrollment_done_) {
+    assistant_manager_service_->UpdateInternalOptions(
+        assistant_manager_service_->assistant_manager_internal());
+
+  } else {
+    // If hotword is enabled but there is no voice model found, launch the
+    // enrollment flow.
+    if (service_->assistant_state()->hotword_enabled().value())
+      service_->assistant_controller()->StartSpeakerIdEnrollmentFlow();
+  }
+}
+
 void AssistantSettingsManagerImpl::HandleStopSpeakerIdEnrollment(
     base::RepeatingCallback<void()> callback) {
   DCHECK(service_->main_task_runner()->RunsTasksInCurrentSequence());
diff --git a/chromeos/services/assistant/assistant_settings_manager_impl.h b/chromeos/services/assistant/assistant_settings_manager_impl.h
index 9f8edba9..0f30b604 100644
--- a/chromeos/services/assistant/assistant_settings_manager_impl.h
+++ b/chromeos/services/assistant/assistant_settings_manager_impl.h
@@ -14,6 +14,7 @@
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 
 namespace assistant_client {
+struct SpeakerIdEnrollmentStatus;
 struct SpeakerIdEnrollmentUpdate;
 }  // namespace assistant_client
 
@@ -53,6 +54,8 @@
   void HandleSpeakerIdEnrollmentUpdate(
       const assistant_client::SpeakerIdEnrollmentUpdate& update);
   void HandleStopSpeakerIdEnrollment(base::RepeatingCallback<void()> callback);
+  void HandleSpeakerIdEnrollmentStatusSync(
+      const assistant_client::SpeakerIdEnrollmentStatus& status);
 
   Service* const service_;
   AssistantManagerServiceImpl* const assistant_manager_service_;
diff --git a/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.cc b/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.cc
index 87033ee..c415ccf 100644
--- a/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.cc
+++ b/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.cc
@@ -10,7 +10,9 @@
 #include "base/bind_helpers.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/driver/sync_auth_util.h"
 #include "components/sync/driver/sync_service.h"
+#include "google_apis/gaia/google_service_auth_error.h"
 
 namespace browser_sync {
 
@@ -24,8 +26,10 @@
       sync_service_(sync_service) {
   DCHECK(type == syncer::AUTOFILL_WALLET_DATA ||
          type == syncer::AUTOFILL_WALLET_METADATA);
-  currently_enabled_ = IsEnabled();
   SubscribeToPrefChanges();
+  // TODO(crbug.com/906995): remove this observing mechanism once all sync
+  // datatypes are stopped by ProfileSyncService, when sync is paused.
+  sync_service_->AddObserver(this);
 }
 
 AutofillWalletModelTypeController::AutofillWalletModelTypeController(
@@ -41,11 +45,15 @@
       sync_service_(sync_service) {
   DCHECK(type == syncer::AUTOFILL_WALLET_DATA ||
          type == syncer::AUTOFILL_WALLET_METADATA);
-  currently_enabled_ = IsEnabled();
   SubscribeToPrefChanges();
+  // TODO(crbug.com/906995): remove this observing mechanism once all sync
+  // datatypes are stopped by ProfileSyncService, when sync is paused.
+  sync_service_->AddObserver(this);
 }
 
-AutofillWalletModelTypeController::~AutofillWalletModelTypeController() {}
+AutofillWalletModelTypeController::~AutofillWalletModelTypeController() {
+  sync_service_->RemoveObserver(this);
+}
 
 void AutofillWalletModelTypeController::Stop(
     syncer::ShutdownReason shutdown_reason,
@@ -66,30 +74,18 @@
 
 bool AutofillWalletModelTypeController::ReadyForStart() const {
   DCHECK(CalledOnValidThread());
-  return currently_enabled_;
+  return pref_service_->GetBoolean(
+             autofill::prefs::kAutofillWalletImportEnabled) &&
+         pref_service_->GetBoolean(
+             autofill::prefs::kAutofillCreditCardEnabled) &&
+         !syncer::IsWebSignout(sync_service_->GetAuthError());
 }
 
 void AutofillWalletModelTypeController::OnUserPrefChanged() {
   DCHECK(CalledOnValidThread());
-
-  bool newly_enabled = IsEnabled();
-  if (currently_enabled_ == newly_enabled) {
-    return;  // No change to sync state.
-  }
-
-  currently_enabled_ = newly_enabled;
   sync_service_->ReadyForStartChanged(type());
 }
 
-bool AutofillWalletModelTypeController::IsEnabled() const {
-  DCHECK(CalledOnValidThread());
-
-  // Require two user-visible prefs to be enabled to sync Wallet data/metadata.
-  return pref_service_->GetBoolean(
-             autofill::prefs::kAutofillWalletImportEnabled) &&
-         pref_service_->GetBoolean(autofill::prefs::kAutofillCreditCardEnabled);
-}
-
 void AutofillWalletModelTypeController::SubscribeToPrefChanges() {
   pref_registrar_.Init(pref_service_);
   pref_registrar_.Add(
@@ -102,4 +98,10 @@
                           base::Unretained(this)));
 }
 
+void AutofillWalletModelTypeController::OnStateChanged(
+    syncer::SyncService* sync) {
+  DCHECK(CalledOnValidThread());
+  sync_service_->ReadyForStartChanged(type());
+}
+
 }  // namespace browser_sync
diff --git a/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.h b/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.h
index ecfd8ec..cee6054e 100644
--- a/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.h
+++ b/components/autofill/core/browser/payments/autofill_wallet_model_type_controller.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/sync/driver/model_type_controller.h"
+#include "components/sync/driver/sync_service_observer.h"
 
 class PrefService;
 
@@ -21,7 +22,8 @@
 namespace browser_sync {
 
 // Controls syncing of AUTOFILL_WALLET_DATA and AUTOFILL_WALLET_METADATA.
-class AutofillWalletModelTypeController : public syncer::ModelTypeController {
+class AutofillWalletModelTypeController : public syncer::ModelTypeController,
+                                          public syncer::SyncServiceObserver {
  public:
   // The delegates and |sync_client| must not be null. Furthermore,
   // |sync_client| must outlive this object.
@@ -43,6 +45,9 @@
             StopCallback callback) override;
   bool ReadyForStart() const override;
 
+  // syncer::SyncServiceObserver implementation.
+  void OnStateChanged(syncer::SyncService* sync) override;
+
  private:
   // Callback for changes to the autofill pref.
   void OnUserPrefChanged();
@@ -55,8 +60,6 @@
 
   PrefChangeRegistrar pref_registrar_;
 
-  bool currently_enabled_;
-
   DISALLOW_COPY_AND_ASSIGN(AutofillWalletModelTypeController);
 };
 
diff --git a/components/data_reduction_proxy/core/common/BUILD.gn b/components/data_reduction_proxy/core/common/BUILD.gn
index 8923dfb..6c121fa6 100644
--- a/components/data_reduction_proxy/core/common/BUILD.gn
+++ b/components/data_reduction_proxy/core/common/BUILD.gn
@@ -10,7 +10,6 @@
 template("common_tmpl") {
   static_library(target_name) {
     sources = [
-      "data_reduction_proxy_bypass_action_list.h",
       "data_reduction_proxy_bypass_protocol.cc",
       "data_reduction_proxy_bypass_protocol.h",
       "data_reduction_proxy_bypass_type_list.h",
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_action_list.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_action_list.h
deleted file mode 100644
index cce40d2..0000000
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_action_list.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This is the list of data reduction proxy bypass actions and their values.
-// These actions are specified in the Chrome-Proxy header. For the enum values,
-// include the file
-// "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h".
-//
-// Here we define the values using a macro BYPASS_ACTION_TYPE, so it can be
-// expanded differently in some places (for example, to automatically
-// map a bypass type value to its symbolic name). As such, new values must be
-// appended and cannot be inserted in the middle as there are instances where
-// we will load data between different builds.
-
-// No action type specified.
-BYPASS_ACTION_TYPE(NONE, 0)
-
-// Attempt to retry the current request while bypassing all Data Reduction
-// Proxies; it does not cause other requests to be bypassed.
-BYPASS_ACTION_TYPE(BLOCK_ONCE, 1)
-
-// Bypass all Data Reduction Proxies for a specified period of time.
-BYPASS_ACTION_TYPE(BLOCK, 2)
-
-// Bypass the current Data Reduction Proxy for a specified period of time.
-BYPASS_ACTION_TYPE(BYPASS, 3)
-
-// This must always be last.
-BYPASS_ACTION_TYPE(MAX, 4)
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
index 4bdb409..1301e2ba7 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_protocol.cc
@@ -226,7 +226,6 @@
   data_reduction_proxy_info->bypass_all = false;
   data_reduction_proxy_info->mark_proxies_as_bad = true;
   data_reduction_proxy_info->bypass_duration = base::TimeDelta::FromMinutes(5);
-  data_reduction_proxy_info->bypass_action = BYPASS_ACTION_TYPE_BYPASS;
   *bypass_type = BYPASS_EVENT_TYPE_MEDIUM;
 
   return true;
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
index 0a9b7ad..505ea5e 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
@@ -314,7 +314,6 @@
           headers, kChromeProxyActionBlock, &proxy_info->bypass_duration)) {
     proxy_info->bypass_all = true;
     proxy_info->mark_proxies_as_bad = true;
-    proxy_info->bypass_action = BYPASS_ACTION_TYPE_BLOCK;
     return true;
   }
 
@@ -323,7 +322,6 @@
           headers, kChromeProxyActionBypass, &proxy_info->bypass_duration)) {
     proxy_info->bypass_all = false;
     proxy_info->mark_proxies_as_bad = true;
-    proxy_info->bypass_action = BYPASS_ACTION_TYPE_BYPASS;
     return true;
   }
 
@@ -336,7 +334,6 @@
     proxy_info->bypass_all = true;
     proxy_info->mark_proxies_as_bad = false;
     proxy_info->bypass_duration = base::TimeDelta();
-    proxy_info->bypass_action = BYPASS_ACTION_TYPE_BLOCK_ONCE;
     return true;
   }
 
@@ -390,7 +387,6 @@
     data_reduction_proxy_info->bypass_all = true;
     data_reduction_proxy_info->mark_proxies_as_bad = false;
     data_reduction_proxy_info->bypass_duration = base::TimeDelta();
-    data_reduction_proxy_info->bypass_action = BYPASS_ACTION_TYPE_BLOCK_ONCE;
     return BYPASS_EVENT_TYPE_URL_REDIRECT_CYCLE;
   }
 
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h
index 45ace3b..a72b270e 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h
@@ -49,23 +49,9 @@
 #undef BYPASS_EVENT_TYPE
 };
 
-// Values for the bypass actions that can be specified by the Data Reduction
-// Proxy in response to a client request. These are explicit bypass actions
-// specified by the Data Reduction Proxy in the Chrome-Proxy header, block-once,
-// bypass=1, block=300, etc. These are not used for Chrome initiated bypasses
-// due to a server error, missing Via header, etc.
-enum DataReductionProxyBypassAction {
-#define BYPASS_ACTION_TYPE(label, value) BYPASS_ACTION_TYPE_##label = value,
-#include "components/data_reduction_proxy/core/common/data_reduction_proxy_bypass_action_list.h"
-#undef BYPASS_ACTION_TYPE
-};
-
 // Contains instructions contained in the Chrome-Proxy header.
 struct DataReductionProxyInfo {
-  DataReductionProxyInfo()
-      : bypass_all(false),
-        mark_proxies_as_bad(false),
-        bypass_action(BYPASS_ACTION_TYPE_NONE) {}
+  DataReductionProxyInfo() : bypass_all(false), mark_proxies_as_bad(false) {}
 
   // True if Chrome should bypass all available data reduction proxies. False
   // if only the currently connected data reduction proxy should be bypassed.
@@ -78,9 +64,6 @@
   // Amount of time to bypass the data reduction proxy or proxies. This value is
   // ignored if |mark_proxies_as_bad| is false.
   base::TimeDelta bypass_duration;
-
-  // The bypass action specified by the data reduction proxy.
-  DataReductionProxyBypassAction bypass_action;
 };
 
 // Gets the header used for data reduction proxy requests and responses.
diff --git a/components/download/internal/common/base_file_win.cc b/components/download/internal/common/base_file_win.cc
index 97a8417..cdaf9d4 100644
--- a/components/download/internal/common/base_file_win.cc
+++ b/components/download/internal/common/base_file_win.cc
@@ -6,262 +6,307 @@
 
 #include <windows.h>
 
-#include <cguid.h>
 #include <objbase.h>
 #include <shellapi.h>
+#include <shobjidl.h>
+#include <wrl/client.h>
 
-#include "base/files/file.h"
-#include "base/files/file_util.h"
-#include "base/guid.h"
-#include "base/macros.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
+#include "base/win/com_init_util.h"
+#include "base/win/iunknown_impl.h"
 #include "components/download/public/common/download_interrupt_reasons_utils.h"
-#include "components/download/public/common/download_stats.h"
 
 namespace download {
 namespace {
 
-// Maps the result of a call to |SHFileOperation()| onto a
-// |DownloadInterruptReason|.
-//
-// These return codes are *old* (as in, DOS era), and specific to
-// |SHFileOperation()|.
-// They do not appear in any windows header.
-//
-// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx.
-DownloadInterruptReason MapShFileOperationCodes(int code) {
-  DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE;
+// By and large the errors seen here are listed in sherrors.h, included from
+// shobjidl.h.
+DownloadInterruptReason HRESULTToDownloadInterruptReason(HRESULT hr) {
+  // S_OK, other success values are aggregated here.
+  if (SUCCEEDED(hr) && HRESULT_FACILITY(hr) != FACILITY_SHELL)
+    return DOWNLOAD_INTERRUPT_REASON_NONE;
 
-  // Check these pre-Win32 error codes first, then check for matches
-  // in Winerror.h.
-  // This switch statement should be kept in sync with the list of codes
-  // above.
-  switch (code) {
-    // Not a pre-Win32 error code; here so that this particular case shows up in
-    // our histograms. Unfortunately, it is used not just to signal actual
-    // ACCESS_DENIED errors, but many other errors as well. So we treat it as a
-    // transient error.
-    case ERROR_ACCESS_DENIED:  // Access is denied.
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
-      break;
+  // All of the remaining HRESULTs to be considered are either from the copy
+  // engine, or are unknown; we've got handling for all the copy engine errors,
+  // and otherwise we'll just return the generic error reason.
+  switch (hr) {
+    case COPYENGINE_S_YES:
+    case COPYENGINE_S_NOT_HANDLED:
+    case COPYENGINE_S_USER_RETRY:
+    case COPYENGINE_S_MERGE:
+    case COPYENGINE_S_DONT_PROCESS_CHILDREN:
+    case COPYENGINE_S_ALREADY_DONE:
+    case COPYENGINE_S_PENDING:
+    case COPYENGINE_S_KEEP_BOTH:
+    case COPYENGINE_S_COLLISIONRESOLVED:
+    case COPYENGINE_S_PROGRESS_PAUSE:
+      return DOWNLOAD_INTERRUPT_REASON_NONE;
 
-    // This isn't documented but returned from SHFileOperation. Sharing
-    // violations indicate that another process had the file open while we were
-    // trying to rename. Anti-virus is believed to be the cause of this error in
-    // the wild. Treated as a transient error on the assumption that the file
-    // will be made available for renaming at a later time.
-    case ERROR_SHARING_VIOLATION:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
-      break;
+    case COPYENGINE_S_CLOSE_PROGRAM:
+      // Like sharing violations, another process is using the file we want to
+      // touch, so wait for it to close.
+    case COPYENGINE_E_SHARING_VIOLATION_SRC:
+    case COPYENGINE_E_SHARING_VIOLATION_DEST:
+      // Sharing violations are encountered when some other process has a file
+      // open; often it's antivirus scanning, and this error can be treated as
+      // transient, as we assume eventually the other process will close its
+      // handle.
+      return DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
 
-    // This is also not a documented return value of SHFileOperation, but has
-    // been observed in the wild. We are treating it as a transient error based
-    // on the cases we have seen so far.  See http://crbug.com/368455.
-    case ERROR_INVALID_PARAMETER:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
-      break;
+    case COPYENGINE_E_PATH_TOO_DEEP_DEST:
+    case COPYENGINE_E_PATH_TOO_DEEP_SRC:
+    case COPYENGINE_E_NEWFILE_NAME_TOO_LONG:
+    case COPYENGINE_E_NEWFOLDER_NAME_TOO_LONG:
+      // Any of these errors can be encountered if MAXPATH is hit while writing
+      // out a filename. This can happen really just about anywhere.
+      return DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
 
-    // The source and destination files are the same file.
-    // DE_SAMEFILE == 0x71
-    case 0x71:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
+    case COPYENGINE_S_USER_IGNORED:
+      // On Windows 7, inability to access a file may return "user ignored"
+      // instead of correctly reporting the failure.
+    case COPYENGINE_E_ACCESS_DENIED_DEST:
+    case COPYENGINE_E_ACCESS_DENIED_SRC:
+      // There's a security problem, or the file is otherwise inaccessible.
+    case COPYENGINE_E_DEST_IS_RO_CD:
+    case COPYENGINE_E_DEST_IS_RW_CD:
+    case COPYENGINE_E_DEST_IS_R_CD:
+    case COPYENGINE_E_DEST_IS_RO_DVD:
+    case COPYENGINE_E_DEST_IS_RW_DVD:
+    case COPYENGINE_E_DEST_IS_R_DVD:
+    case COPYENGINE_E_SRC_IS_RO_CD:
+    case COPYENGINE_E_SRC_IS_RW_CD:
+    case COPYENGINE_E_SRC_IS_R_CD:
+    case COPYENGINE_E_SRC_IS_RO_DVD:
+    case COPYENGINE_E_SRC_IS_RW_DVD:
+    case COPYENGINE_E_SRC_IS_R_DVD:
+      // When the source is actually a disk, and a Move is attempted, it can't
+      // delete the source. This is unlikely to be encountered in our scenario.
+      return DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
 
-    // The operation was canceled by the user, or silently canceled if the
-    // appropriate flags were supplied to SHFileOperation.
-    // DE_OPCANCELLED == 0x75
-    case 0x75:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
+    case COPYENGINE_E_FILE_TOO_LARGE:
+    case COPYENGINE_E_DISK_FULL:
+    case COPYENGINE_E_REMOVABLE_FULL:
+    case COPYENGINE_E_DISK_FULL_CLEAN:
+      // No room for the file in the destination location.
+      return DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
 
-    // Security settings denied access to the source.
-    // DE_ACCESSDENIEDSRC == 0x78
-    case 0x78:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The source or destination path exceeded or would exceed MAX_PATH.
-    // DE_PATHTOODEEP == 0x79
-    case 0x79:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
-      break;
-
-    // The path in the source or destination or both was invalid.
-    // DE_INVALIDFILES == 0x7C
-    case 0x7C:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The destination path is an existing file.
-    // DE_FLDDESTISFILE == 0x7E
-    case 0x7E:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The destination path is an existing folder.
-    // DE_FILEDESTISFLD == 0x80
-    case 0x80:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The name of the file exceeds MAX_PATH.
-    // DE_FILENAMETOOLONG == 0x81
-    case 0x81:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
-      break;
-
-    // The destination is a read-only CD-ROM, possibly unformatted.
-    // DE_DEST_IS_CDROM == 0x82
-    case 0x82:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The destination is a read-only DVD, possibly unformatted.
-    // DE_DEST_IS_DVD == 0x83
-    case 0x83:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The destination is a writable CD-ROM, possibly unformatted.
-    // DE_DEST_IS_CDRECORD == 0x84
-    case 0x84:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The file involved in the operation is too large for the destination
-    // media or file system.
-    // DE_FILE_TOO_LARGE == 0x85
-    case 0x85:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
-      break;
-
-    // The source is a read-only CD-ROM, possibly unformatted.
-    // DE_SRC_IS_CDROM == 0x86
-    case 0x86:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The source is a read-only DVD, possibly unformatted.
-    // DE_SRC_IS_DVD == 0x87
-    case 0x87:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // The source is a writable CD-ROM, possibly unformatted.
-    // DE_SRC_IS_CDRECORD == 0x88
-    case 0x88:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
-      break;
-
-    // MAX_PATH was exceeded during the operation.
-    // DE_ERROR_MAX == 0xB7
-    case 0xB7:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
-      break;
-
-    // An unspecified error occurred on the destination.
-    // XE_ERRORONDEST == 0x10000
-    case 0x10000:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // Multiple file paths were specified in the source buffer, but only one
-    // destination file path.
-    // DE_MANYSRC1DEST == 0x72
-    case 0x72:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // Rename operation was specified but the destination path is
-    // a different directory. Use the move operation instead.
-    // DE_DIFFDIR == 0x73
-    case 0x73:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The source is a root directory, which cannot be moved or renamed.
-    // DE_ROOTDIR == 0x74
-    case 0x74:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The destination is a subtree of the source.
-    // DE_DESTSUBTREE == 0x76
-    case 0x76:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The operation involved multiple destination paths,
-    // which can fail in the case of a move operation.
-    // DE_MANYDEST == 0x7A
-    case 0x7A:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // The source and destination have the same parent folder.
-    // DE_DESTSAMETREE == 0x7D
-    case 0x7D:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // An unknown error occurred.  This is typically due to an invalid path in
-    // the source or destination.  This error does not occur on Windows Vista
-    // and later.
-    // DE_UNKNOWN_ERROR == 0x402
-    case 0x402:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
-
-    // Destination is a root directory and cannot be renamed.
-    // DE_ROOTDIR | ERRORONDEST == 0x10074
-    case 0x10074:
-      result = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-      break;
+    case COPYENGINE_E_ALREADY_EXISTS_NORMAL:
+    case COPYENGINE_E_ALREADY_EXISTS_READONLY:
+    case COPYENGINE_E_ALREADY_EXISTS_SYSTEM:
+    case COPYENGINE_E_ALREADY_EXISTS_FOLDER:
+      // The destination already exists and can't be replaced.
+    case COPYENGINE_E_INVALID_FILES_SRC:
+    case COPYENGINE_E_INVALID_FILES_DEST:
+      // Either the source or destination file was invalid.
+    case COPYENGINE_E_STREAM_LOSS:
+    case COPYENGINE_E_EA_LOSS:
+    case COPYENGINE_E_PROPERTY_LOSS:
+    case COPYENGINE_E_PROPERTIES_LOSS:
+    case COPYENGINE_E_ENCRYPTION_LOSS:
+      // The destination can't support some functionality that the file needs.
+      // The interesting one here is E_STREAM_LOSS, especially with MOTW.
+    case COPYENGINE_E_FLD_IS_FILE_DEST:
+    case COPYENGINE_E_FILE_IS_FLD_DEST:
+      // There is an existing file with the same name as a new folder, and
+      // vice versa.
+    case COPYENGINE_E_ROOT_DIR_DEST:
+    case COPYENGINE_E_ROOT_DIR_SRC:
+    case COPYENGINE_E_DIFF_DIR:
+    case COPYENGINE_E_SAME_FILE:
+    case COPYENGINE_E_MANY_SRC_1_DEST:
+    case COPYENGINE_E_DEST_SUBTREE:
+    case COPYENGINE_E_DEST_SAME_TREE:
+    case COPYENGINE_E_USER_CANCELLED:
+    case COPYENGINE_E_CANCELLED:
+    case COPYENGINE_E_REQUIRES_ELEVATION:
+      return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
   }
 
-  if (result != DOWNLOAD_INTERRUPT_REASON_NONE)
-    return result;
+  // Copy operations may still return Win32 error codes, so handle those here.
+  if (HRESULT_FACILITY(hr) == FACILITY_WIN32) {
+    return ConvertFileErrorToInterruptReason(
+        base::File::OSErrorToFileError(HRESULT_CODE(hr)));
+  }
 
-  // If not one of the above codes, it should be a standard Windows error code.
-  return ConvertFileErrorToInterruptReason(
-      base::File::OSErrorToFileError(code));
+  return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
 }
 
+class FileOperationProgressSink : public base::win::IUnknownImpl,
+                                  public IFileOperationProgressSink {
+ public:
+  FileOperationProgressSink() = default;
+
+  HRESULT GetOperationResult() { return result_; }
+
+  // base::win::IUnknownImpl:
+  STDMETHODIMP QueryInterface(REFIID riid, PVOID* ppv) override {
+    if (riid == IID_IFileOperationProgressSink) {
+      *ppv = static_cast<IFileOperationProgressSink*>(this);
+      IUnknownImpl::AddRef();
+      return S_OK;
+    }
+
+    return IUnknownImpl::QueryInterface(riid, ppv);
+  }
+
+  ULONG STDMETHODCALLTYPE AddRef() override { return IUnknownImpl::AddRef(); }
+  ULONG STDMETHODCALLTYPE Release() override { return IUnknownImpl::Release(); }
+
+  // IFileOperationProgressSink:
+  HRESULT STDMETHODCALLTYPE FinishOperations(HRESULT hr) override {
+    // If a failure has already been captured, don't bother overriding it. That
+    // way, the original failure can be propagated; in the event that the new
+    // HRESULT is also a success, overwriting will not harm anything and
+    // captures the final state of the whole operation.
+    if (SUCCEEDED(result_))
+      result_ = hr;
+    return S_OK;
+  }
+
+  HRESULT STDMETHODCALLTYPE PauseTimer() override { return S_OK; }
+  HRESULT STDMETHODCALLTYPE PostCopyItem(DWORD,
+                                         IShellItem*,
+                                         IShellItem*,
+                                         PCWSTR,
+                                         HRESULT,
+                                         IShellItem*) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PostDeleteItem(DWORD,
+                                           IShellItem*,
+                                           HRESULT,
+                                           IShellItem*) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PostMoveItem(DWORD,
+                                         IShellItem*,
+                                         IShellItem*,
+                                         PCWSTR,
+                                         HRESULT hr,
+                                         IShellItem*) override {
+    // Like in FinishOperations, overwriting with a different success value
+    // does not have a negative impact, but replacing an existing failure will
+    // cause issues.
+    if (SUCCEEDED(result_))
+      result_ = hr;
+    return S_OK;
+  }
+  HRESULT STDMETHODCALLTYPE PostNewItem(DWORD,
+                                        IShellItem*,
+                                        PCWSTR,
+                                        PCWSTR,
+                                        DWORD,
+                                        HRESULT,
+                                        IShellItem*) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  PostRenameItem(DWORD, IShellItem*, PCWSTR, HRESULT, IShellItem*) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PreCopyItem(DWORD,
+                                        IShellItem*,
+                                        IShellItem*,
+                                        PCWSTR) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PreDeleteItem(DWORD, IShellItem*) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PreMoveItem(DWORD,
+                                        IShellItem*,
+                                        IShellItem*,
+                                        PCWSTR) override {
+    return S_OK;
+  }
+  HRESULT STDMETHODCALLTYPE PreNewItem(DWORD, IShellItem*, PCWSTR) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE PreRenameItem(DWORD, IShellItem*, PCWSTR) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE ResetTimer() override { return S_OK; }
+  HRESULT STDMETHODCALLTYPE ResumeTimer() override { return S_OK; }
+  HRESULT STDMETHODCALLTYPE StartOperations() override { return S_OK; }
+  HRESULT STDMETHODCALLTYPE UpdateProgress(UINT, UINT) override { return S_OK; }
+
+ protected:
+  ~FileOperationProgressSink() override = default;
+
+ private:
+  HRESULT result_ = S_OK;
+
+  DISALLOW_COPY_AND_ASSIGN(FileOperationProgressSink);
+};
+
 }  // namespace
 
-// Renames a file using the SHFileOperation API to ensure that the target file
-// gets the correct default security descriptor in the new path.
+// Renames a file using IFileOperation::MoveItem() to ensure that the target
+// file gets the correct default security descriptor in the new path.
 // Returns a network error, or net::OK for success.
 DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
     const base::FilePath& new_path) {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
-  // The parameters to SHFileOperation must be terminated with 2 NULL chars.
-  base::FilePath::StringType source = full_path_.value();
-  base::FilePath::StringType target = new_path.value();
+  base::win::AssertComInitialized();
+  Microsoft::WRL::ComPtr<IShellItem> original_path;
+  HRESULT hr = SHCreateItemFromParsingName(full_path_.value().c_str(), nullptr,
+                                           IID_PPV_ARGS(&original_path));
 
-  source.append(1, L'\0');
-  target.append(1, L'\0');
+  // |new_path| can be broken down to provide the new folder, as well as the
+  // new filename. We'll start with the folder, which the caller should ensure
+  // exists.
+  Microsoft::WRL::ComPtr<IShellItem> destination_folder;
+  if (SUCCEEDED(hr)) {
+    hr =
+        SHCreateItemFromParsingName(new_path.DirName().value().c_str(), nullptr,
+                                    IID_PPV_ARGS(&destination_folder));
+  }
 
-  SHFILEOPSTRUCT move_info = {nullptr};
-  move_info.wFunc = FO_MOVE;
-  move_info.pFrom = source.c_str();
-  move_info.pTo = target.c_str();
-  move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI |
-                     FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS;
+  Microsoft::WRL::ComPtr<IFileOperation> file_operation;
+  if (SUCCEEDED(hr)) {
+    hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_INPROC_SERVER,
+                          IID_PPV_ARGS(&file_operation));
+  }
 
-  int result = SHFileOperation(&move_info);
-  DownloadInterruptReason interrupt_reason = DOWNLOAD_INTERRUPT_REASON_NONE;
+  if (SUCCEEDED(hr)) {
+    // Don't show any UI, don't migrate security attributes (use the
+    // destination's attributes), and stop on first error-retaining the original
+    // failure reason.
+    hr = file_operation->SetOperationFlags(
+        FOF_NO_UI | FOF_NOCOPYSECURITYATTRIBS | FOFX_EARLYFAILURE);
+  }
 
-  if (result == 0 && move_info.fAnyOperationsAborted)
-    interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
-  else if (result != 0)
-    interrupt_reason = MapShFileOperationCodes(result);
+  scoped_refptr<FileOperationProgressSink> sink(new FileOperationProgressSink);
+  if (SUCCEEDED(hr)) {
+    hr = file_operation->MoveItem(original_path.Get(), destination_folder.Get(),
+                                  new_path.BaseName().value().c_str(),
+                                  sink.get());
+  }
+
+  if (SUCCEEDED(hr))
+    hr = file_operation->PerformOperations();
+
+  if (SUCCEEDED(hr))
+    hr = sink->GetOperationResult();
+
+  // Convert HRESULT to DownloadInterruptReason.
+  DownloadInterruptReason interrupt_reason =
+      HRESULTToDownloadInterruptReason(hr);
+
+  if (interrupt_reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
+    // The operation could still have been aborted; we can't get a better reason
+    // at this point, but we've got more information to go by.
+    BOOL any_operations_aborted = TRUE;
+    file_operation->GetAnyOperationsAborted(&any_operations_aborted);
+    if (any_operations_aborted)
+      interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
+  }
 
   if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE)
-    return LogInterruptReason("SHFileOperation", result, interrupt_reason);
+    return LogInterruptReason("IFileOperation::MoveItem", hr, interrupt_reason);
+
   return interrupt_reason;
 }
 
diff --git a/components/feedback/feedback_common.cc b/components/feedback/feedback_common.cc
index 58bc46c7..41f4149 100644
--- a/components/feedback/feedback_common.cc
+++ b/components/feedback/feedback_common.cc
@@ -42,6 +42,8 @@
 constexpr char kPngMimeType[] = "image/png";
 constexpr char kArbitraryMimeType[] = "application/octet-stream";
 
+constexpr char kGoogleDotCom[] = "@google.com";
+
 // Determine if the given feedback value is small enough to not need to
 // be compressed.
 bool BelowCompressionThreshold(const std::string& content) {
@@ -66,7 +68,8 @@
 
     // We must avoid adding the crash IDs to the system_logs.txt file for
     // privacy reasons. They should just be part of the product specific data.
-    if (key == feedback::FeedbackReport::kCrashReportIdsKey)
+    if (key == feedback::FeedbackReport::kCrashReportIdsKey ||
+        key == feedback::FeedbackReport::kAllCrashReportIdsKey)
       continue;
 
     if (value.find("\n") != std::string::npos) {
@@ -242,6 +245,15 @@
 
   for (const auto& iter : logs_) {
     if (BelowCompressionThreshold(iter.second)) {
+      // We only send the list of all the crash report IDs if the user has a
+      // @google.com email. We do this also in feedback_private_api, but not all
+      // code paths go through that so we need to check again here.
+      if (iter.first == feedback::FeedbackReport::kAllCrashReportIdsKey &&
+          !base::EndsWith(user_email(), kGoogleDotCom,
+                          base::CompareCase::INSENSITIVE_ASCII)) {
+        continue;
+      }
+
       // Small enough logs should end up in the report data itself. However,
       // they're still added as part of the system_logs.zip file.
       AddFeedbackData(feedback_data, iter.first, iter.second);
diff --git a/components/feedback/feedback_common_unittest.cc b/components/feedback/feedback_common_unittest.cc
index 539bd8f7..3d9a78ec 100644
--- a/components/feedback/feedback_common_unittest.cc
+++ b/components/feedback/feedback_common_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/feedback/feedback_common.h"
 
 #include "base/bind.h"
+#include "components/feedback/feedback_report.h"
 #include "components/feedback/proto/common.pb.h"
 #include "components/feedback/proto/dom.pb.h"
 #include "components/feedback/proto/extension.pb.h"
@@ -101,3 +102,19 @@
   EXPECT_EQ(kLogsAttachmentName,
             report_.product_specific_binary_data(0).name());
 }
+
+TEST_F(FeedbackCommonTest, TestAllCrashIdsRemoval) {
+  feedback_->AddLog(feedback::FeedbackReport::kAllCrashReportIdsKey, kOne);
+  feedback_->set_user_email("nobody@example.com");
+  feedback_->PrepareReport(&report_);
+
+  EXPECT_EQ(0, report_.web_data().product_specific_data_size());
+}
+
+TEST_F(FeedbackCommonTest, TestAllCrashIdsRetention) {
+  feedback_->AddLog(feedback::FeedbackReport::kAllCrashReportIdsKey, kOne);
+  feedback_->set_user_email("nobody@google.com");
+  feedback_->PrepareReport(&report_);
+
+  EXPECT_EQ(1, report_.web_data().product_specific_data_size());
+}
diff --git a/components/feedback/feedback_report.cc b/components/feedback/feedback_report.cc
index 4b0d804..c1f362f 100644
--- a/components/feedback/feedback_report.cc
+++ b/components/feedback/feedback_report.cc
@@ -61,6 +61,9 @@
 const char FeedbackReport::kCrashReportIdsKey[]  = "crash_report_ids";
 
 // static
+const char FeedbackReport::kAllCrashReportIdsKey[] = "all_crash_report_ids";
+
+// static
 void FeedbackReport::LoadReportsAndQueue(const base::FilePath& user_dir,
                                          const QueueCallback& callback) {
   if (user_dir.empty())
diff --git a/components/feedback/feedback_report.h b/components/feedback/feedback_report.h
index 1e330cab..413e03b 100644
--- a/components/feedback/feedback_report.h
+++ b/components/feedback/feedback_report.h
@@ -38,6 +38,10 @@
   // the feedback server.
   static const char kCrashReportIdsKey[];
 
+  // The ID of the product specific data for the list of all crash report IDs as
+  // stored by the feedback server. Only used for @google.com emails.
+  static const char kAllCrashReportIdsKey[];
+
   // Loads the reports still on disk and queues then using the given callback.
   // This call blocks on the file reads.
   static void LoadReportsAndQueue(const base::FilePath& user_dir,
diff --git a/components/feedback/system_logs/system_logs_fetcher.cc b/components/feedback/system_logs/system_logs_fetcher.cc
index efeba4b..b493738f 100644
--- a/components/feedback/system_logs/system_logs_fetcher.cc
+++ b/components/feedback/system_logs/system_logs_fetcher.cc
@@ -23,7 +23,6 @@
 // not be anonymized.
 constexpr const char* const kWhitelistedKeysOfUUIDs[] = {
     "CHROMEOS_BOARD_APPID", "CHROMEOS_CANARY_APPID", "CHROMEOS_RELEASE_APPID",
-    "CLIENT_ID",
 };
 
 // Returns true if the given |key| is anonymizer-whitelisted and whose
diff --git a/components/password_manager/core/browser/form_parsing/form_parser.cc b/components/password_manager/core/browser/form_parsing/form_parser.cc
index f77ab0cd..51bea67 100644
--- a/components/password_manager/core/browser/form_parsing/form_parser.cc
+++ b/components/password_manager/core/browser/form_parsing/form_parser.cc
@@ -223,13 +223,22 @@
          significant_fields.confirmation_password == field;
 }
 
-// Returns the first element of |fields| which has the specified
-// |unique_renderer_id|, or null if there is no such element.
-ProcessedField* FindFieldWithUniqueRendererId(
-    std::vector<ProcessedField>* processed_fields,
-    uint32_t unique_renderer_id) {
+bool DoesPredictionCorrespondToField(
+    const FormFieldData& field,
+    const PasswordFieldPrediction& prediction) {
+#if defined(OS_IOS)
+  return field.unique_id == prediction.unique_id;
+#else
+  return field.unique_renderer_id == prediction.renderer_id;
+#endif
+}
+
+// Returns the first element of |fields| which corresponds to |prediction|, or
+// null if there is no such element.
+ProcessedField* FindField(std::vector<ProcessedField>* processed_fields,
+                          const PasswordFieldPrediction& prediction) {
   for (ProcessedField& processed_field : *processed_fields) {
-    if (processed_field.field->unique_renderer_id == unique_renderer_id)
+    if (DoesPredictionCorrespondToField(*processed_field.field, prediction))
       return &processed_field;
   }
   return nullptr;
@@ -266,14 +275,12 @@
     switch (field_type) {
       case CredentialFieldType::kUsername:
         if (!result->username) {
-          processed_field = FindFieldWithUniqueRendererId(
-              processed_fields, prediction.renderer_id);
+          processed_field = FindField(processed_fields, prediction);
           if (processed_field) {
             result->username = processed_field->field;
           }
         } else if (!second_username) {
-          processed_field = FindFieldWithUniqueRendererId(
-              processed_fields, prediction.renderer_id);
+          processed_field = FindField(processed_fields, prediction);
           if (processed_field) {
             second_username = processed_field->field;
           }
@@ -285,8 +292,7 @@
         if (result->password) {
           prevent_handling_two_usernames = true;
         } else {
-          processed_field = FindFieldWithUniqueRendererId(
-              processed_fields, prediction.renderer_id);
+          processed_field = FindField(processed_fields, prediction);
           if (processed_field) {
             if (!processed_field->is_password)
               continue;
@@ -306,8 +312,7 @@
         // before the user has thought of and typed their new password
         // elsewhere. See https://crbug.com/902700 for more details.
         if (!result->new_password) {
-          processed_field = FindFieldWithUniqueRendererId(
-              processed_fields, prediction.renderer_id);
+          processed_field = FindField(processed_fields, prediction);
           if (processed_field) {
             if (!processed_field->is_password)
               continue;
@@ -316,8 +321,7 @@
         }
         break;
       case CredentialFieldType::kConfirmationPassword:
-        processed_field = FindFieldWithUniqueRendererId(processed_fields,
-                                                        prediction.renderer_id);
+        processed_field = FindField(processed_fields, prediction);
         if (processed_field) {
           if (!processed_field->is_password)
             continue;
@@ -357,8 +361,7 @@
   for (const PasswordFieldPrediction& prediction : predictions) {
     if (prediction.type == autofill::CREDIT_CARD_VERIFICATION_CODE ||
         prediction.type == autofill::NOT_PASSWORD) {
-      ProcessedField* processed_field = FindFieldWithUniqueRendererId(
-          processed_fields, prediction.renderer_id);
+      ProcessedField* processed_field = FindField(processed_fields, prediction);
       if (processed_field)
         processed_field->server_hints_not_password = true;
     }
diff --git a/components/password_manager/core/browser/form_parsing/form_parser_unittest.cc b/components/password_manager/core/browser/form_parsing/form_parser_unittest.cc
index 894dc06..de0a845 100644
--- a/components/password_manager/core/browser/form_parsing/form_parser_unittest.cc
+++ b/components/password_manager/core/browser/form_parsing/form_parser_unittest.cc
@@ -183,6 +183,9 @@
       field.name = ASCIIToUTF16(field_description.name);
     }
     field.name_attribute = field.name;
+#if defined(OS_IOS)
+    field.unique_id = StampUniqueSuffix("unique_id");
+#endif
     field.form_control_type = field_description.form_control_type;
     field.is_focusable = field_description.is_focusable;
     field.is_enabled = field_description.is_enabled;
@@ -212,6 +215,9 @@
     if (field_description.prediction.type != autofill::MAX_VALID_FIELD_TYPE) {
       predictions->push_back(field_description.prediction);
       predictions->back().renderer_id = renderer_id;
+#if defined(OS_IOS)
+      predictions->back().unique_id = field.unique_id;
+#endif
     }
     if (field_description.predicted_username >= 0) {
       size_t index = static_cast<size_t>(field_description.predicted_username);
diff --git a/components/password_manager/core/browser/form_parsing/password_field_prediction.cc b/components/password_manager/core/browser/form_parsing/password_field_prediction.cc
index 3cae5de..7f46f57 100644
--- a/components/password_manager/core/browser/form_parsing/password_field_prediction.cc
+++ b/components/password_manager/core/browser/form_parsing/password_field_prediction.cc
@@ -90,6 +90,9 @@
           {.renderer_id = field->unique_renderer_id,
            .type = server_type,
            .may_use_prefilled_placeholder = may_use_prefilled_placeholder});
+#if defined(OS_IOS)
+      result.back().unique_id = field->unique_id;
+#endif
     }
   }
 
diff --git a/components/password_manager/core/browser/form_parsing/password_field_prediction.h b/components/password_manager/core/browser/form_parsing/password_field_prediction.h
index 50adf03e..d92f876 100644
--- a/components/password_manager/core/browser/form_parsing/password_field_prediction.h
+++ b/components/password_manager/core/browser/form_parsing/password_field_prediction.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include <vector>
 
+#include "build/build_config.h"
 #include "components/autofill/core/browser/field_types.h"
 
 namespace autofill {
@@ -30,7 +31,11 @@
 
 // Contains server predictions for a field.
 struct PasswordFieldPrediction {
+  // Field identifier generated in Blink on non-iOS platforms.
   uint32_t renderer_id;
+#if defined(OS_IOS)
+  base::string16 unique_id;
+#endif
   autofill::ServerFieldType type;
   bool may_use_prefilled_placeholder = false;
 };
diff --git a/components/policy/core/common/cloud/cloud_policy_client.cc b/components/policy/core/common/cloud/cloud_policy_client.cc
index c6dc10b..94f2c6c 100644
--- a/components/policy/core/common/cloud/cloud_policy_client.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client.cc
@@ -556,10 +556,11 @@
 void CloudPolicyClient::UploadDeviceStatus(
     const em::DeviceStatusReportRequest* device_status,
     const em::SessionStatusReportRequest* session_status,
+    const em::ChildStatusReportRequest* child_status,
     const CloudPolicyClient::StatusCallback& callback) {
   CHECK(is_registered());
   // Should pass in at least one type of status.
-  DCHECK(device_status || session_status);
+  DCHECK(device_status || session_status || child_status);
   std::unique_ptr<DeviceManagementRequestJob> request_job(service_->CreateJob(
       DeviceManagementRequestJob::TYPE_UPLOAD_STATUS, GetURLLoaderFactory()));
   request_job->SetAuthData(DMAuth::FromDMToken(dm_token_));
@@ -572,6 +573,8 @@
     *request->mutable_device_status_report_request() = *device_status;
   if (session_status)
     *request->mutable_session_status_report_request() = *session_status;
+  if (child_status)
+    *request->mutable_child_status_report_request() = *child_status;
 
   const DeviceManagementRequestJob::Callback job_callback =
       base::AdaptCallbackForRepeating(base::BindOnce(
diff --git a/components/policy/core/common/cloud/cloud_policy_client.h b/components/policy/core/common/cloud/cloud_policy_client.h
index f8487a72..04dc912 100644
--- a/components/policy/core/common/cloud/cloud_policy_client.h
+++ b/components/policy/core/common/cloud/cloud_policy_client.h
@@ -224,13 +224,13 @@
   virtual void UploadEnterpriseEnrollmentId(const std::string& enrollment_id,
                                             const StatusCallback& callback);
 
-  // Uploads device/session status to the server. As above, the client must be
-  // in a registered state. If non-null, |device_status| and |session_status|
-  // will be included in the upload status request. The |callback| will be
-  // called when the operation completes.
+  // Uploads status to the server. The client must be in a registered state.
+  // Only non-null statuses will be included in the upload status request. The
+  // |callback| will be called when the operation completes.
   virtual void UploadDeviceStatus(
       const enterprise_management::DeviceStatusReportRequest* device_status,
       const enterprise_management::SessionStatusReportRequest* session_status,
+      const enterprise_management::ChildStatusReportRequest* child_status,
       const StatusCallback& callback);
 
   // Uploads Chrome Desktop report to the server. As above, the client must be
diff --git a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
index e00ff4b..378cb59 100644
--- a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc
@@ -206,6 +206,7 @@
 
     upload_status_request_.mutable_device_status_report_request();
     upload_status_request_.mutable_session_status_report_request();
+    upload_status_request_.mutable_child_status_report_request();
 
     chrome_desktop_report_request_.mutable_chrome_desktop_report_request();
 
@@ -1160,7 +1161,9 @@
       base::Unretained(&callback_observer_));
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
   EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
 }
 
@@ -1177,7 +1180,9 @@
                           base::Unretained(&callback_observer_));
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
   EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
 
   // Tests that previous OAuth token is no longer sent in status upload after
@@ -1186,7 +1191,8 @@
 
   ExpectUploadStatus();
   EXPECT_CALL(callback_observer_, OnCallbackComplete(true)).Times(1);
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
   EXPECT_EQ(DM_STATUS_SUCCESS, client_->status());
 }
 
@@ -1204,7 +1210,9 @@
       base::Unretained(&callback_observer_));
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
 
   // Now initiate a policy fetch - this should not cancel the upload job.
   ExpectPolicyFetch(kDMToken);
@@ -1259,7 +1267,9 @@
       base::Unretained(&callback_observer_));
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
 
   // Set up pending upload certificate job.
   MockDeviceManagementJob* upload_certificate_job = nullptr;
@@ -1304,7 +1314,9 @@
 
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
   EXPECT_EQ(DM_STATUS_REQUEST_FAILED, client_->status());
 }
 
@@ -1323,7 +1335,9 @@
       base::Unretained(&callback_observer_));
   em::DeviceStatusReportRequest device_status;
   em::SessionStatusReportRequest session_status;
-  client_->UploadDeviceStatus(&device_status, &session_status, callback);
+  em::ChildStatusReportRequest child_status;
+  client_->UploadDeviceStatus(&device_status, &session_status, &child_status,
+                              callback);
   EXPECT_EQ(1, client_->GetActiveRequestCountForTest());
   EXPECT_CALL(observer_, OnRegistrationStateChanged(_));
   ExpectUnregistration(kDMToken);
diff --git a/components/policy/core/common/cloud/mock_cloud_policy_client.h b/components/policy/core/common/cloud/mock_cloud_policy_client.h
index 9de7d08f..7de1e5ca 100644
--- a/components/policy/core/common/cloud/mock_cloud_policy_client.h
+++ b/components/policy/core/common/cloud/mock_cloud_policy_client.h
@@ -48,9 +48,10 @@
                void(const std::string&, const StatusCallback&));
   MOCK_METHOD2(UploadEnterpriseEnrollmentId,
                void(const std::string&, const StatusCallback&));
-  MOCK_METHOD3(UploadDeviceStatus,
+  MOCK_METHOD4(UploadDeviceStatus,
                void(const enterprise_management::DeviceStatusReportRequest*,
                     const enterprise_management::SessionStatusReportRequest*,
+                    const enterprise_management::ChildStatusReportRequest*,
                     const StatusCallback&));
   MOCK_METHOD2(UploadAppInstallReport,
                void(const enterprise_management::AppInstallReportRequest*,
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index cb3ea15..c2544df9 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -1972,6 +1972,58 @@
 // account.
 message RefreshAccountResponse {}
 
+// Models a window for screen time.
+message ScreenTimeSpan {
+  optional TimePeriod time_period = 1;
+
+  // The actual activity duration during a particular time period window
+  // (in milliseconds).
+  optional int64 active_duration_ms = 2;
+}
+
+// Informs the server about the current state of a child user's session, to
+// allow parent supervision.
+message ChildStatusReportRequest {
+  // The user's DMToken.
+  optional string user_dm_token = 1;
+
+  // Timestamp of this status report in milliseconds since epoch.
+  optional int64 timestamp_ms = 2;
+
+  // Time zone id of the active user (e.g. America/Sao_Paulo).
+  // For more details check `third_party/icu/source/i18n/unicode/timezone.h`.
+  optional string time_zone = 3;
+
+  // A list of time spans when the screen was on during the user's session.
+  repeated ScreenTimeSpan screen_time_span = 4;
+
+  // Information about ARC status.
+  optional AndroidStatus android_status = 5;
+
+  // The OS version reported by the device is a platform version
+  // e.g. 1435.0.2011_12_16_1635.
+  optional string os_version = 6;
+
+  // "Verified", "Dev". Same as verified mode.
+  // If the mode is unknown, this field should not be set.
+  optional string boot_mode = 7;
+
+  // Next id: 8.
+}
+
+// Response from DMServer to update user devices' status.
+// It is possible that status report fails but policy request succeed.  In such
+// case, the ChildStatusReportResponse will contain an error code and the
+// device should re-send status report data in the next policy request.  The
+// device should re-send report data if policy request fails, even if
+// ChildStatusReportResponse contains no error code.
+message ChildStatusReportResponse {
+  optional int32 error_code = 1;
+
+  // Human readable error message for customer support purpose.
+  optional string error_message = 2;
+}
+
 // Request from the DMAgent on the device to the DMServer.  This is
 // container for all requests from device to server.  The overall HTTP
 // request MUST be in the following format:
@@ -2044,7 +2096,8 @@
 //   ping: policy_request
 //   policy: policy_request
 //   register: register_request
-//   status: device_status_report_request or session_status_report_request
+//   status: device_status_report_request or session_status_report_request or
+//       child_status_report_request
 //   unregister: unregister_request
 //   remote_commands: remote_command_request
 //   attribute_update_permission: device_attribute_update_permission_request
@@ -2062,7 +2115,6 @@
 //   policy_validation_report: policy_validation_report_request
 //   device_initial_enrollment_state: device_initial_enrollment_state_request
 //   refresh_account: refresh_account_request
-//
 message DeviceManagementRequest {
   reserved 24;  // unused previous version of chrome_desktop_report_request.
 
@@ -2078,6 +2130,7 @@
   // Update status.
   optional DeviceStatusReportRequest device_status_report_request = 4;
   optional SessionStatusReportRequest session_status_report_request = 5;
+  optional ChildStatusReportRequest child_status_report_request = 30;
 
   // Auto-enrollment detection.
   optional DeviceAutoEnrollmentRequest auto_enrollment_request = 6;
@@ -2154,6 +2207,8 @@
 
   // Request from device to wipe an old account and get a new account.
   optional RefreshAccountRequest refresh_account_request = 29;
+
+  // Next id: 31.
 }
 
 // Response from server to device.
@@ -2197,11 +2252,10 @@
   // Policy response.
   optional DevicePolicyResponse policy_response = 5;
 
-  // Device status report response.
+  // Update status report response.
   optional DeviceStatusReportResponse device_status_report_response = 6;
-
-  // Session status report response.
   optional SessionStatusReportResponse session_status_report_response = 7;
+  optional ChildStatusReportResponse child_status_report_response = 29;
 
   // Auto-enrollment detection response.
   optional DeviceAutoEnrollmentResponse auto_enrollment_response = 8;
@@ -2269,4 +2323,6 @@
 
   // Response to refresh account request.
   optional RefreshAccountResponse refresh_account_response = 28;
+
+  // Next id: 30.
 }
diff --git a/components/previews/content/hint_cache.cc b/components/previews/content/hint_cache.cc
index 29ad4cfc..83e68dc 100644
--- a/components/previews/content/hint_cache.cc
+++ b/components/previews/content/hint_cache.cc
@@ -87,9 +87,9 @@
 }
 
 std::unique_ptr<HintCacheStore::ComponentUpdateData>
-HintCache::CreateUpdateDataForFetchedHints() const {
+HintCache::CreateUpdateDataForFetchedHints(base::Time update_time) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return hint_store_->CreateUpdateDataForFetchedHints();
+  return hint_store_->CreateUpdateDataForFetchedHints(update_time);
 }
 
 void HintCache::UpdateComponentData(
@@ -108,16 +108,18 @@
 
 bool HintCache::StoreFetchedHints(
     std::unique_ptr<optimization_guide::proto::GetHintsResponse>
-        get_hints_response) {
+        get_hints_response,
+    base::Time update_time,
+    base::OnceClosure callback) {
   std::unique_ptr<HintCacheStore::ComponentUpdateData>
-      fetched_hints_update_data = CreateUpdateDataForFetchedHints();
-  if (!ProcessGetHintsResponse(get_hints_response.get(),
-                               fetched_hints_update_data.get())) {
-    return false;
+      fetched_hints_update_data = CreateUpdateDataForFetchedHints(update_time);
+  if (ProcessGetHintsResponse(get_hints_response.get(),
+                              fetched_hints_update_data.get())) {
+    hint_store_->UpdateFetchedHintsData(std::move(fetched_hints_update_data),
+                                        std::move(callback));
+    return true;
   }
-
-  // TODO(mcrouse): Provide the |hint_store_| with UpdateData to stored.
-  return true;
+  return false;
 }
 
 bool HintCache::HasHint(const std::string& host) const {
@@ -171,6 +173,13 @@
   return nullptr;
 }
 
+base::Time HintCache::FetchedHintsUpdateTime() const {
+  if (!hint_store_) {
+    return base::Time();
+  }
+  return hint_store_->FetchedHintsUpdateTime();
+}
+
 void HintCache::OnStoreInitialized(base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   std::move(callback).Run();
diff --git a/components/previews/content/hint_cache.h b/components/previews/content/hint_cache.h
index b21749b9..55f1ed2 100644
--- a/components/previews/content/hint_cache.h
+++ b/components/previews/content/hint_cache.h
@@ -56,9 +56,10 @@
   // hints. No version is needed nor applicable for fetched hints. During
   // processing of the GetHintsResponse, hints are moved into the update data.
   // After processing is complete, the update data is provided to the backing
-  // store to update hints.
+  // store to update hints. |update_time| specifies when the hints within the
+  // created update data will be scheduled to be updated.
   std::unique_ptr<HintCacheStore::ComponentUpdateData>
-  CreateUpdateDataForFetchedHints() const;
+  CreateUpdateDataForFetchedHints(base::Time update_time) const;
 
   // Updates the store's component data using the provided ComponentUpdateData
   // and asynchronously runs the provided callback after the update finishes.
@@ -66,13 +67,18 @@
       std::unique_ptr<HintCacheStore::ComponentUpdateData> component_data,
       base::OnceClosure callback);
 
-  // Process |get_hints_response| to be stored in the hint cache store.
-  // Returns true if processing get_hints_response is successful and applicable
-  // hints can be stored. Returns false if there are no applicable hints in
-  // |get_hints_response| or it cannot be processed.
+  // Process |get_hints_response| to be stored in the hint cache store. Returns
+  // true if processing |get_hints_response| is successful and applicable hints
+  // can be stored. Returns false if there are no applicable hints in
+  // |get_hints_response| or it cannot be processed. |callback| is
+  // asynchronously run when the hints are successfully stored or if the store
+  // is not available. |update_time| specifies when the hints within
+  // |get_hints_response| will need to be updated next.
   bool StoreFetchedHints(
       std::unique_ptr<optimization_guide::proto::GetHintsResponse>
-          get_hints_response);
+          get_hints_response,
+      base::Time update_time,
+      base::OnceClosure callback);
 
   // Returns whether the cache has a hint data for |host| locally (whether
   // in memory or persisted on disk).
@@ -82,6 +88,11 @@
   // |callback| if/when loaded.
   void LoadHint(const std::string& host, HintLoadedCallback callback);
 
+  // Returns the update time provided by |hint_store_|, which specifies when the
+  // fetched hints within the store are ready to be updated. If |hint_store_| is
+  // not initialized, base::Time() is returned.
+  base::Time FetchedHintsUpdateTime() const;
+
   // Returns the hint data for |host| if found in memory, otherwise nullptr.
   const optimization_guide::proto::Hint* GetHintIfLoaded(
       const std::string& host);
diff --git a/components/previews/content/hint_cache_store.cc b/components/previews/content/hint_cache_store.cc
index d5283d3d..6b78079e 100644
--- a/components/previews/content/hint_cache_store.cc
+++ b/components/previews/content/hint_cache_store.cc
@@ -6,6 +6,8 @@
 
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
 #include "components/previews/content/proto/hint_cache.pb.h"
 
@@ -42,7 +44,9 @@
   kSchemaMetadataMissing = 2,
   kSchemaMetadataWrongVersion = 3,
   kComponentMetadataMissing = 4,
-  kMaxValue = kComponentMetadataMissing,
+  kFetchedMetadataMissing = 5,
+  kComponentAndFetchedMetadataMissing = 6,
+  kMaxValue = kComponentAndFetchedMetadataMissing,
 };
 
 // Util class for recording the result of loading the metadata. The result is
@@ -68,6 +72,12 @@
   UMA_HISTOGRAM_ENUMERATION("Previews.HintCacheLevelDBStore.Status", status);
 }
 
+// Returns true if |key_prefix| is a prefix of |key|.
+bool DatabasePrefixFilter(const std::string& key_prefix,
+                          const std::string& key) {
+  return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
+}
+
 }  // namespace
 
 HintCacheStore::HintCacheStore(
@@ -142,11 +152,11 @@
 }
 
 std::unique_ptr<HintCacheStore::ComponentUpdateData>
-HintCacheStore::CreateUpdateDataForFetchedHints() const {
+HintCacheStore::CreateUpdateDataForFetchedHints(base::Time update_time) const {
   // TODO(mcrouse): Currently returns a LevelDBComponentUpdateData, future
-  // refactor will create a LevelDBFetchedHintsData that will take a cache
-  // expiry time. The version for this object will be ignored.
-  return std::make_unique<LevelDBComponentUpdateData>(base::Version("0.0.1"));
+  // refactor will enable the construction of UpdateData with the settings
+  // necessary for the type of hint being updated, fetched or component.
+  return std::make_unique<LevelDBComponentUpdateData>(update_time);
 }
 
 void HintCacheStore::UpdateComponentData(
@@ -209,6 +219,37 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void HintCacheStore::UpdateFetchedHintsData(
+    std::unique_ptr<ComponentUpdateData> fetched_hints_data,
+    base::OnceClosure callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(fetched_hints_data);
+  DCHECK(!component_data_update_in_flight_);
+
+  if (!IsAvailable()) {
+    std::move(callback).Run();
+    return;
+  }
+
+  fetched_update_time_ = fetched_hints_data->update_time();
+
+  component_data_update_in_flight_ = true;
+
+  hint_entry_keys_.reset();
+
+  LevelDBComponentUpdateData* leveldb_fetched_hints_data =
+      static_cast<LevelDBComponentUpdateData*>(fetched_hints_data.get());
+
+  // This will remove the fetched metadata entry and insert all the entries
+  // currently in |leveldb_fetched_hints_data|.
+  database_->UpdateEntriesWithRemoveFilter(
+      std::move(leveldb_fetched_hints_data->entries_to_save_),
+      base::BindRepeating(&DatabasePrefixFilter,
+                          GetMetadataTypeEntryKey(MetadataType::kFetched)),
+      base::BindOnce(&HintCacheStore::OnUpdateComponentData,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
 bool HintCacheStore::FindHintEntryKey(const std::string& host_suffix,
                                       EntryKey* out_hint_entry_key) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -240,6 +281,14 @@
                                      hint_entry_key, std::move(callback)));
 }
 
+base::Time HintCacheStore::FetchedHintsUpdateTime() const {
+  // If the store is not available, the metadata entries have not been loaded
+  // so there are no fetched hints.
+  if (!IsAvailable())
+    return base::Time();
+  return fetched_update_time_;
+}
+
 HintCacheStore::LevelDBComponentUpdateData::LevelDBComponentUpdateData(
     const base::Version& version)
     : ComponentUpdateData(version),
@@ -254,6 +303,21 @@
       std::move(metadata_component_entry));
 }
 
+HintCacheStore::LevelDBComponentUpdateData::LevelDBComponentUpdateData(
+    base::Time update_time)
+    : ComponentUpdateData(update_time),
+      component_hint_entry_key_prefix_(GetFetchedHintEntryKeyPrefix()),
+      entries_to_save_(std::make_unique<EntryVector>()) {
+  // Add fetched metadata entry
+  previews::proto::StoreEntry metadata_fetched_entry;
+  metadata_fetched_entry.set_update_time_secs(
+      update_time.ToDeltaSinceWindowsEpoch().InSeconds());
+
+  entries_to_save_->emplace_back(
+      GetMetadataTypeEntryKey(MetadataType::kFetched),
+      std::move(metadata_fetched_entry));
+}
+
 HintCacheStore::LevelDBComponentUpdateData::~LevelDBComponentUpdateData() =
     default;
 
@@ -299,6 +363,12 @@
          component_version.GetString() + kKeySectionDelimiter;
 }
 
+// static
+HintCacheStore::EntryKeyPrefix HintCacheStore::GetFetchedHintEntryKeyPrefix() {
+  return base::NumberToString(static_cast<int>(EntryType::kFetchedHint)) +
+         kKeySectionDelimiter;
+}
+
 void HintCacheStore::UpdateStatus(Status new_status) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -483,6 +553,7 @@
 
   // If the component metadata entry exists, then use it to set the component
   // version.
+  bool component_metadata_missing = false;
   auto component_entry =
       metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kComponent));
   if (component_entry != metadata_entries->end()) {
@@ -491,6 +562,26 @@
   } else {
     result_recorder.set_result(PreviewsHintCacheLevelDBStoreLoadMetadataResult::
                                    kComponentMetadataMissing);
+    component_metadata_missing = true;
+  }
+
+  auto fetched_entry =
+      metadata_entries->find(GetMetadataTypeEntryKey(MetadataType::kFetched));
+  if (fetched_entry != metadata_entries->end()) {
+    DCHECK(fetched_entry->second.has_update_time_secs());
+    fetched_update_time_ = base::Time::FromDeltaSinceWindowsEpoch(
+        base::TimeDelta::FromSeconds(fetched_entry->second.update_time_secs()));
+  } else {
+    if (component_metadata_missing) {
+      result_recorder.set_result(
+          PreviewsHintCacheLevelDBStoreLoadMetadataResult::
+              kComponentAndFetchedMetadataMissing);
+    } else {
+      result_recorder.set_result(
+          PreviewsHintCacheLevelDBStoreLoadMetadataResult::
+              kFetchedMetadataMissing);
+    }
+    fetched_update_time_ = base::Time();
   }
 
   UpdateStatus(Status::kAvailable);
diff --git a/components/previews/content/hint_cache_store.h b/components/previews/content/hint_cache_store.h
index c5310dd5..224d315 100644
--- a/components/previews/content/hint_cache_store.h
+++ b/components/previews/content/hint_cache_store.h
@@ -71,9 +71,12 @@
         : version_(version) {
       DCHECK(version_.IsValid());
     }
+    explicit ComponentUpdateData(base::Time update_time)
+        : update_time_(update_time) {}
     virtual ~ComponentUpdateData() = default;
 
     const base::Version& version() const { return version_; }
+    base::Time update_time() const { return update_time_; }
 
     // Pure virtual function for moving a hint into ComponentUpdateData. After
     // MoveHintIntoUpdateData() is called, |hint| is no longer valid.
@@ -83,6 +86,9 @@
    private:
     // The component version of the update data.
     base::Version version_;
+
+    // The time when hints in the update data need to be updated.
+    base::Time update_time_;
   };
 
   HintCacheStore(const base::FilePath& database_dir,
@@ -114,7 +120,7 @@
   // Service so the store can expire old hints, remove hints specified by the
   // server, and store the fresh hints.
   std::unique_ptr<HintCacheStore::ComponentUpdateData>
-  CreateUpdateDataForFetchedHints() const;
+  CreateUpdateDataForFetchedHints(base::Time update_time) const;
 
   // Updates the component data (both version and hints) contained within the
   // store. When this is called, all pre-existing component data within the
@@ -124,6 +130,18 @@
   void UpdateComponentData(std::unique_ptr<ComponentUpdateData> component_data,
                            base::OnceClosure callback);
 
+  // Updates the fetched hints data contained in the store, including the
+  // metadata entry. The callback is run asynchronously after the database
+  // stores the hints.
+  //
+  // TODO(mcrouse): When called, fetched hint data in the store that has expired
+  // specified by |expiry_time_secs| will be purged and only the new hints and
+  // non-expired hints are retained.
+
+  void UpdateFetchedHintsData(
+      std::unique_ptr<ComponentUpdateData> fetched_hints_data,
+      base::OnceClosure callback);
+
   // Finds a hint entry key associated with the specified host suffix. Returns
   // true if a hint entry key is found, in which case |out_hint_entry_key| is
   // populated with the key.
@@ -137,6 +155,10 @@
   // asynchronous.
   void LoadHint(const EntryKey& hint_entry_key, HintLoadedCallback callback);
 
+  // Returns the time that the fetched hints in the store can be updated. If
+  // |this| is not available, base::Time() is returned.
+  base::Time FetchedHintsUpdateTime() const;
+
  private:
   friend class HintCacheStoreTest;
   friend class HintUpdateData;
@@ -165,6 +187,7 @@
   enum class EntryType {
     kMetadata = 1,
     kComponentHint = 2,
+    kFetchedHint = 3,
   };
 
   // Metadata types within the store. The metadata type appears at the end of
@@ -178,17 +201,23 @@
   enum class MetadataType {
     kSchema = 1,
     kComponent = 2,
+    kFetched = 3,
   };
 
   // HintCacheStore's concrete implementation of ComponentUpdateData.
-  // LevelDBComponentUpdateData is private within HintCacheStore. All
-  // classes outside of HintCacheStore can only interact with the
-  // ComponentUpdateData base class. LevelDBComponentUpdateData is created by
-  // HintCacheStore when MaybeCreateComponentUpdateData() is called and
-  // used to update the store's component data during UpdateComponentData().
+  // LevelDBComponentUpdateData is private within HintCacheStore. All classes
+  // outside of HintCacheStore can only interact with the ComponentUpdateData
+  // base class. LevelDBComponentUpdateData is created by HintCacheStore when
+  // MaybeCreateComponentUpdateData() is called and used to update the store's
+  // component data during UpdateComponentData().
+  //
+  // TODO(mcrouse): Bug: 932707.
+  // This class should be refactored so that there is a single constructor and
+  // the base class removed. The class will also be moved out of |this|.
   class LevelDBComponentUpdateData : public ComponentUpdateData {
    public:
     explicit LevelDBComponentUpdateData(const base::Version& version);
+    explicit LevelDBComponentUpdateData(base::Time update_time);
     ~LevelDBComponentUpdateData() override;
 
     // ComponentUpdateData overrides:
@@ -227,6 +256,9 @@
   static EntryKeyPrefix GetComponentHintEntryKeyPrefix(
       const base::Version& component_version);
 
+  // Returns prefix of the key of every fetched hint entry: "3_".
+  static EntryKeyPrefix GetFetchedHintEntryKeyPrefix();
+
   // Updates the status of the store to the specified value, validates the
   // transition, and destroys the database in the case where the status
   // transitions to Status::kFailed.
@@ -331,6 +363,10 @@
   // is true, keys and hints will not be returned by the store.
   bool component_data_update_in_flight_;
 
+  // The next update time for the fetched hints that are currently in the
+  // store.
+  base::Time fetched_update_time_;
+
   // The keys of the hints available within the store.
   std::unique_ptr<EntryKeySet> hint_entry_keys_;
 
diff --git a/components/previews/content/hint_cache_store_unittest.cc b/components/previews/content/hint_cache_store_unittest.cc
index 3dc531ba..0541e5d4 100644
--- a/components/previews/content/hint_cache_store_unittest.cc
+++ b/components/previews/content/hint_cache_store_unittest.cc
@@ -57,7 +57,9 @@
   // Initializes the entries contained within the database on startup.
   void SeedInitialData(
       MetadataSchemaState state,
-      base::Optional<size_t> component_hint_count = base::Optional<size_t>()) {
+      base::Optional<size_t> component_hint_count = base::Optional<size_t>(),
+      base::Optional<base::Time> fetched_hints_update =
+          base::Optional<base::Time>()) {
     db_store_.clear();
 
     // Add a metadata schema entry if its state isn't kMissing. The version
@@ -76,7 +78,9 @@
     // If the database is being seeded with component hints, it is indicated
     // with a provided count. Add the component metadata with the default
     // component version and then add the indicated number of component hints.
-    if (component_hint_count) {
+    // if (component_hint_count && component_hint_count >
+    // static_cast<size_t>(0)) {
+    if (component_hint_count && component_hint_count > 0u) {
       db_store_[HintCacheStore::GetMetadataTypeEntryKey(
                     HintCacheStore::MetadataType::kComponent)]
           .set_version(kDefaultComponentVersion);
@@ -93,6 +97,12 @@
         page_hint->set_page_pattern("page pattern " + std::to_string(i));
       }
     }
+    if (fetched_hints_update) {
+      db_store_[HintCacheStore::GetMetadataTypeEntryKey(
+                    HintCacheStore::MetadataType::kFetched)]
+          .set_update_time_secs(
+              fetched_hints_update->ToDeltaSinceWindowsEpoch().InSeconds());
+    }
   }
 
   // Moves the specified number of component hints into the update data.
@@ -173,6 +183,23 @@
         HintCacheStore::MetadataType::kSchema));
   }
 
+  // Verifies that the fetched metadata has the expected next update time.
+  void ExpectFetchedMetadata(base::Time update_time) const {
+    const auto& metadata_entry =
+        db_store_.find(HintCacheStore::GetMetadataTypeEntryKey(
+            HintCacheStore::MetadataType::kFetched));
+    if (metadata_entry != db_store_.end()) {
+      // The next update time should have same time up to the second as the
+      // metadata entry is stored in seconds.
+      EXPECT_TRUE(
+          base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromSeconds(
+              metadata_entry->second.update_time_secs())) -
+              update_time <
+          base::TimeDelta::FromSeconds(1));
+    } else {
+      FAIL() << "No fetched metadata found";
+    }
+  }
   // Verifies that the component metadata has the expected version and all
   // expected component hints are present.
   void ExpectComponentHintsPresent(const std::string& version,
@@ -437,7 +464,7 @@
 TEST_F(HintCacheStoreTest, InitializeFailedOnLoadHintEntryKeysWithInitialData) {
   base::HistogramTester histogram_tester;
 
-  SeedInitialData(MetadataSchemaState::kValid, 10);
+  SeedInitialData(MetadataSchemaState::kValid, 10, base::Time().Now());
   CreateDatabase();
   InitializeDatabase(true /*=success*/);
 
@@ -535,7 +562,13 @@
 
   histogram_tester.ExpectBucketCount(
       "Previews.HintCacheLevelDBStore.LoadMetadataResult",
-      4 /* kComponentMetadataMissing */, 1);
+      4 /* kComponentMetadataMissing*/, 0);
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      5 /* kFetchedMetadataMissing*/, 0);
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      6 /* kComponentAndFetchedMetadataMissing*/, 1);
 
   histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
                                      0 /* kUninitialized */, 1);
@@ -610,6 +643,38 @@
 
   MetadataSchemaState schema_state = MetadataSchemaState::kValid;
   size_t component_hint_count = 10;
+  SeedInitialData(schema_state, component_hint_count, base::Time().Now());
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  // The store should contain the schema metadata entry, the component metadata
+  // entry, and all of the initial component hints.
+  EXPECT_EQ(GetDBStoreEntryCount(),
+            static_cast<size_t>(component_hint_count + 3));
+  EXPECT_EQ(GetStoreHintEntryKeyCount(), component_hint_count);
+
+  EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
+  ExpectComponentHintsPresent(kDefaultComponentVersion, component_hint_count);
+
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult", 0 /* kSuccess */, 1);
+
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     0 /* kUninitialized */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     1 /* kInitializing */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     2 /* kAvailable */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     3 /* kFailed */, 0);
+}
+
+TEST_F(HintCacheStoreTest,
+       InitializeSucceededWithValidSchemaEntryAndComponentDataOnly) {
+  base::HistogramTester histogram_tester;
+
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  size_t component_hint_count = 10;
   SeedInitialData(schema_state, component_hint_count);
   CreateDatabase();
   InitializeStore(schema_state);
@@ -624,7 +689,46 @@
   ExpectComponentHintsPresent(kDefaultComponentVersion, component_hint_count);
 
   histogram_tester.ExpectBucketCount(
-      "Previews.HintCacheLevelDBStore.LoadMetadataResult", 0 /* kSuccess */, 1);
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      4 /* kComponentMetadataMissing*/, 0);
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      5 /* kFetchedMetadataMissing*/, 1);
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      6 /* kComponentAndFetchedMetadataMissing*/, 0);
+
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     0 /* kUninitialized */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     1 /* kInitializing */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     2 /* kAvailable */, 1);
+  histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
+                                     3 /* kFailed */, 0);
+}
+
+TEST_F(HintCacheStoreTest,
+       InitializeSucceededWithValidSchemaEntryAndFetchedMetaData) {
+  base::HistogramTester histogram_tester;
+
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  size_t component_hint_count = 0;
+  SeedInitialData(schema_state, component_hint_count, base::Time().Now());
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  // The store should contain the schema metadata entry, the component metadata
+  // entry, and all of the initial component hints.
+  EXPECT_EQ(GetDBStoreEntryCount(),
+            static_cast<size_t>(component_hint_count + 2));
+  EXPECT_EQ(GetStoreHintEntryKeyCount(), component_hint_count);
+
+  EXPECT_TRUE(IsMetadataSchemaEntryKeyPresent());
+
+  histogram_tester.ExpectBucketCount(
+      "Previews.HintCacheLevelDBStore.LoadMetadataResult",
+      4 /* kComponentMetadataMissing*/, 1);
 
   histogram_tester.ExpectBucketCount("Previews.HintCacheLevelDBStore.Status",
                                      0 /* kUninitialized */, 1);
@@ -1010,4 +1114,13 @@
   }
 }
 
+TEST_F(HintCacheStoreTest, FetchedHintsMetadataStored) {
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  base::Time update_time = base::Time().Now();
+  SeedInitialData(schema_state, 10, update_time);
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  ExpectFetchedMetadata(update_time);
+}
 }  // namespace previews
diff --git a/components/previews/content/hint_cache_unittest.cc b/components/previews/content/hint_cache_unittest.cc
index 8415412..fa28b9c 100644
--- a/components/previews/content/hint_cache_unittest.cc
+++ b/components/previews/content/hint_cache_unittest.cc
@@ -59,13 +59,16 @@
     loaded_hint_ = nullptr;
     is_store_initialized_ = false;
     is_component_data_updated_ = false;
-    on_load_hint_callback_called = false;
+    on_load_hint_callback_called_ = false;
+    is_fetched_data_stored_ = false;
 
     RunUntilIdle();
   }
 
   HintCache* hint_cache() { return hint_cache_.get(); }
 
+  bool is_fetched_data_stored() { return is_fetched_data_stored_; }
+
   // Updates the cache with |component_data| and waits for callback indicating
   // that the update is complete.
   void UpdateComponentData(
@@ -82,21 +85,27 @@
 
   bool StoreFetchedHints(
       std::unique_ptr<optimization_guide::proto::GetHintsResponse>
-          get_hints_response) {
-    bool result = hint_cache_->StoreFetchedHints(std::move(get_hints_response));
+          get_hints_response,
+      base::Time stored_time) {
+    is_fetched_data_stored_ = false;
+    bool result = hint_cache_->StoreFetchedHints(
+        std::move(get_hints_response), stored_time,
+        base::BindOnce(&HintCacheTest::OnHintStored, base::Unretained(this)));
 
     RunUntilIdle();
     return result;
   }
 
+  void OnHintStored() { is_fetched_data_stored_ = true; }
+
   // Loads hint for the specified host from the cache and waits for callback
   // indicating that loading the hint is complete.
   void LoadHint(const std::string& host) {
-    on_load_hint_callback_called = false;
+    on_load_hint_callback_called_ = false;
     loaded_hint_ = nullptr;
     hint_cache_->LoadHint(host, base::BindOnce(&HintCacheTest::OnLoadHint,
                                                base::Unretained(this)));
-    while (!on_load_hint_callback_called) {
+    while (!on_load_hint_callback_called_) {
       RunUntilIdle();
     }
   }
@@ -114,7 +123,7 @@
   void OnStoreInitialized() { is_store_initialized_ = true; }
   void OnUpdateComponentData() { is_component_data_updated_ = true; }
   void OnLoadHint(const optimization_guide::proto::Hint* hint) {
-    on_load_hint_callback_called = true;
+    on_load_hint_callback_called_ = true;
     loaded_hint_ = hint;
   }
 
@@ -126,7 +135,8 @@
 
   bool is_store_initialized_;
   bool is_component_data_updated_;
-  bool on_load_hint_callback_called;
+  bool on_load_hint_callback_called_;
+  bool is_fetched_data_stored_;
 
   DISALLOW_COPY_AND_ASSIGN(HintCacheTest);
 };
@@ -486,10 +496,13 @@
   EXPECT_EQ(hint_key, GetLoadedHint()->key());
 }
 
-TEST_F(HintCacheTest, ParseValidFetchedHints) {
+TEST_F(HintCacheTest, StoreValidFetchedHints) {
   const int kMemoryCacheSize = 5;
   CreateAndInitializeHintCache(kMemoryCacheSize);
 
+  // Default update time for empty hint cache store is base::Time().
+  EXPECT_EQ(hint_cache()->FetchedHintsUpdateTime(), base::Time());
+
   std::unique_ptr<optimization_guide::proto::GetHintsResponse>
       get_hints_response =
           std::make_unique<optimization_guide::proto::GetHintsResponse>();
@@ -500,7 +513,12 @@
   optimization_guide::proto::PageHint* page_hint = hint->add_page_hints();
   page_hint->set_page_pattern("page pattern");
 
-  EXPECT_TRUE(StoreFetchedHints(std::move(get_hints_response)));
+  base::Time stored_time = base::Time().Now();
+  EXPECT_TRUE(StoreFetchedHints(std::move(get_hints_response), stored_time));
+  EXPECT_TRUE(is_fetched_data_stored());
+
+  // Next update time for hints should be updated.
+  EXPECT_EQ(hint_cache()->FetchedHintsUpdateTime(), stored_time);
 }
 
 TEST_F(HintCacheTest, ParseEmptyFetchedHints) {
@@ -511,7 +529,9 @@
       get_hints_response =
           std::make_unique<optimization_guide::proto::GetHintsResponse>();
 
-  EXPECT_FALSE(StoreFetchedHints(std::move(get_hints_response)));
+  EXPECT_FALSE(
+      StoreFetchedHints(std::move(get_hints_response), base::Time().Now()));
+  EXPECT_FALSE(is_fetched_data_stored());
 }
 
 }  // namespace
diff --git a/components/previews/content/hints_fetcher.cc b/components/previews/content/hints_fetcher.cc
index fe5f2e36..c6b54ba 100644
--- a/components/previews/content/hints_fetcher.cc
+++ b/components/previews/content/hints_fetcher.cc
@@ -144,6 +144,8 @@
   if (net_status == net::OK && response_code == net::HTTP_OK &&
       get_hints_response->ParseFromString(get_hints_response_data)) {
     std::move(hints_fetched_callback_).Run(std::move(get_hints_response));
+  } else {
+    std::move(hints_fetched_callback_).Run(base::nullopt);
   }
 }
 
diff --git a/components/previews/content/hints_fetcher.h b/components/previews/content/hints_fetcher.h
index 9a8258e..1df02b5 100644
--- a/components/previews/content/hints_fetcher.h
+++ b/components/previews/content/hints_fetcher.h
@@ -12,6 +12,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/previews/core/previews_experiments.h"
@@ -35,19 +36,21 @@
   // to pass back the fetched hints response from the remote Optimization Guide
   // Service.
   using HintsFetchedCallback = base::OnceCallback<void(
-      std::unique_ptr<optimization_guide::proto::GetHintsResponse>)>;
+      base::Optional<
+          std::unique_ptr<optimization_guide::proto::GetHintsResponse>>)>;
 
  public:
   HintsFetcher(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       GURL optimization_guide_service_url);
-  ~HintsFetcher();
+  virtual ~HintsFetcher();
 
-  // Requests hints from the Optimization Guide Service if a request for
-  // them is not already in progress. Returns whether a new request was
-  // issued. |hints_fetched_callback| is only run if a fetch was successful
-  // and GetHintsResponse can be returned.
-  bool FetchOptimizationGuideServiceHints(
+  // Requests hints from the Optimization Guide Service if a request for them is
+  // not already in progress. Returns whether a new request was issued.
+  // |hints_fetched_callback| is run, passing a GetHintsResponse object, if a
+  // fetch was successful or passes nullopt if the fetch fails. Virtualized for
+  // testing.
+  virtual bool FetchOptimizationGuideServiceHints(
       const std::vector<std::string>& hosts,
       HintsFetchedCallback hints_fetched_callback);
 
diff --git a/components/previews/content/hints_fetcher_unittest.cc b/components/previews/content/hints_fetcher_unittest.cc
index b1dd3dd7..71042934 100644
--- a/components/previews/content/hints_fetcher_unittest.cc
+++ b/components/previews/content/hints_fetcher_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
@@ -42,12 +43,14 @@
   ~HintsFetcherTest() override {}
 
   void OnHintsFetched(
-      std::unique_ptr<optimization_guide::proto::GetHintsResponse>
+      base::Optional<
+          std::unique_ptr<optimization_guide::proto::GetHintsResponse>>
           get_hints_response) {
-    hints_fetched_ = true;
+    if (get_hints_response)
+      hints_fetched_ = true;
   }
 
-  bool HintsFetched() { return hints_fetched_; }
+  bool hints_fetched() { return hints_fetched_; }
 
  protected:
   bool FetchHints(const std::vector<std::string>& hosts) {
@@ -99,7 +102,7 @@
   EXPECT_TRUE(FetchHints(std::vector<std::string>()));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
-  EXPECT_TRUE(HintsFetched());
+  EXPECT_TRUE(hints_fetched());
 }
 
 // Tests to ensure that multiple hint fetches by the same object cannot be in
@@ -124,7 +127,7 @@
 
   // Send a 404 to HintsFetcher.
   SimulateResponse(response_content, net::HTTP_NOT_FOUND);
-  EXPECT_FALSE(HintsFetched());
+  EXPECT_FALSE(hints_fetched());
 }
 
 TEST_F(HintsFetcherTest, FetchReturnBadResponse) {
@@ -132,7 +135,7 @@
   EXPECT_TRUE(FetchHints(std::vector<std::string>()));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
-  EXPECT_FALSE(HintsFetched());
+  EXPECT_FALSE(hints_fetched());
 }
 
 }  // namespace previews
diff --git a/components/previews/content/previews_decider_impl_unittest.cc b/components/previews/content/previews_decider_impl_unittest.cc
index 2612a1d..0d2cd87 100644
--- a/components/previews/content/previews_decider_impl_unittest.cc
+++ b/components/previews/content/previews_decider_impl_unittest.cc
@@ -22,6 +22,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
@@ -151,11 +152,13 @@
   TestPreviewsOptimizationGuide(
       optimization_guide::OptimizationGuideService* optimization_guide_service,
       const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
+      const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
       const base::FilePath& test_path,
       PreviewsTopHostProvider* previews_top_host_provider,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
       : PreviewsOptimizationGuide(optimization_guide_service,
                                   ui_task_runner,
+                                  background_task_runner,
                                   test_path,
                                   previews_top_host_provider,
                                   url_loader_factory) {}
@@ -402,6 +405,8 @@
         std::make_unique<TestPreviewsOptimizationGuide>(
             &optimization_guide_service_,
             scoped_task_environment_.GetMainThreadTaskRunner(),
+            base::CreateSequencedTaskRunnerWithTraits(
+                {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
             temp_dir_.GetPath(), &previews_top_host_provider_,
             url_loader_factory_),
         base::BindRepeating(&IsPreviewFieldTrialEnabled),
diff --git a/components/previews/content/previews_optimization_guide.cc b/components/previews/content/previews_optimization_guide.cc
index 8e32ca6..98369b9 100644
--- a/components/previews/content/previews_optimization_guide.cc
+++ b/components/previews/content/previews_optimization_guide.cc
@@ -9,8 +9,10 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
 #include "base/task/post_task.h"
 #include "base/task_runner_util.h"
+#include "base/time/default_clock.h"
 #include "components/optimization_guide/hints_component_info.h"
 #include "components/optimization_guide/optimization_guide_service.h"
 #include "components/optimization_guide/proto/hints.pb.h"
@@ -34,6 +36,15 @@
 // will have a newer version than it.
 constexpr char kManualConfigComponentVersion[] = "0.0.0";
 
+// Delay between retries on failed fetch and store of hints from the remote
+// Optimization Guide Service.
+constexpr base::TimeDelta kFetchRetryDelay = base::TimeDelta::FromMinutes(15);
+
+// Delay until successfully fetched hints should be updated by requesting from
+// the remote Optimization Guide Service.
+constexpr base::TimeDelta kUpdateFetchedHintsDelay =
+    base::TimeDelta::FromHours(24);
+
 // Hints are purged during startup if the explicit purge switch exists or if
 // a proto override is being used--in which case the hints need to come from the
 // override instead.
@@ -79,25 +90,60 @@
   return proto_configuration;
 }
 
+// Parses a list of hosts to have hints fetched for. This overrides scheduling
+// of the first hints fetch and forces it to occur immediately. If no hosts are
+// provided, nullopt is returned.
+base::Optional<std::vector<std::string>>
+ParseHintsFetchOverrideFromCommandLine() {
+  base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+  if (!cmd_line->HasSwitch(switches::kFetchHintsOverride))
+    return base::nullopt;
+
+  std::string override_hosts_value =
+      cmd_line->GetSwitchValueASCII(switches::kFetchHintsOverride);
+
+  std::vector<std::string> hosts =
+      base::SplitString(override_hosts_value, ",", base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_NONEMPTY);
+
+  if (hosts.size() == 0)
+    return base::nullopt;
+
+  return hosts;
+}
+
+// Provides a random time delta in seconds between |kFetchRandomMinDelay| and
+// |kFetchRandomMaxDelay|.
+base::TimeDelta RandomFetchDelay() {
+  constexpr int kFetchRandomMinDelaySecs = 30;
+  constexpr int kFetchRandomMaxDelaySecs = 60;
+  return base::TimeDelta::FromSeconds(
+      base::RandInt(kFetchRandomMinDelaySecs, kFetchRandomMaxDelaySecs));
+}
+
 }  // namespace
 
 PreviewsOptimizationGuide::PreviewsOptimizationGuide(
     optimization_guide::OptimizationGuideService* optimization_guide_service,
     const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
+    const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
     const base::FilePath& profile_path,
     PreviewsTopHostProvider* previews_top_host_provider,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
     : optimization_guide_service_(optimization_guide_service),
       ui_task_runner_(ui_task_runner),
-      background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
-          {base::MayBlock(), base::TaskPriority::BEST_EFFORT})),
+      background_task_runner_(background_task_runner),
       hint_cache_(std::make_unique<HintCache>(
           std::make_unique<HintCacheStore>(profile_path,
                                            background_task_runner_))),
       previews_top_host_provider_(previews_top_host_provider),
+      time_clock_(base::DefaultClock::GetInstance()),
       url_loader_factory_(url_loader_factory),
       ui_weak_ptr_factory_(this) {
   DCHECK(optimization_guide_service_);
+  // TODO(mcrouse): This needs to be a pref to persist the last fetch attempt
+  // time and prevent crash loops.
+  last_fetch_attempt_ = base::Time();
   hint_cache_->Initialize(
       ShouldPurgeHintCacheStoreOnStartup(),
       base::BindOnce(&PreviewsOptimizationGuide::OnHintCacheInitialized,
@@ -259,30 +305,53 @@
 }
 
 void PreviewsOptimizationGuide::FetchHints() {
-  std::vector<std::string> top_hosts = previews_top_host_provider_->GetTopHosts(
-      previews::params::MaxHostsForOptimizationGuideServiceHintsFetch());
+  base::Optional<std::vector<std::string>> top_hosts =
+      ParseHintsFetchOverrideFromCommandLine();
+  if (!top_hosts) {
+    top_hosts = previews_top_host_provider_->GetTopHosts(
+        previews::params::MaxHostsForOptimizationGuideServiceHintsFetch());
+  }
   DCHECK_GE(previews::params::MaxHostsForOptimizationGuideServiceHintsFetch(),
-            top_hosts.size());
+            top_hosts->size());
+
   if (!hints_fetcher_) {
     hints_fetcher_ = std::make_unique<HintsFetcher>(
         url_loader_factory_, params::GetOptimizationGuideServiceURL());
   }
 
-  hints_fetcher_->FetchOptimizationGuideServiceHints(
-      top_hosts, base::BindOnce(&PreviewsOptimizationGuide::OnHintsFetched,
-                                ui_weak_ptr_factory_.GetWeakPtr()));
+  if (top_hosts->size() > 0) {
+    hints_fetcher_->FetchOptimizationGuideServiceHints(
+        *top_hosts, base::BindOnce(&PreviewsOptimizationGuide::OnHintsFetched,
+                                   ui_weak_ptr_factory_.GetWeakPtr()));
+  }
 }
 
 void PreviewsOptimizationGuide::OnHintsFetched(
-    std::unique_ptr<optimization_guide::proto::GetHintsResponse>
+    base::Optional<std::unique_ptr<optimization_guide::proto::GetHintsResponse>>
         get_hints_response) {
-  DCHECK(get_hints_response);
-
   // TODO(mcrouse): this will be dropped into a backgroundtask as it will likely
   // be intensive/slow storing hints.
-  // The callback will be UpdateHints().
+  if (get_hints_response) {
+    hint_cache_->StoreFetchedHints(
+        std::move(*get_hints_response),
+        time_clock_->Now() + kUpdateFetchedHintsDelay,
+        base::BindOnce(&PreviewsOptimizationGuide::OnFetchedHintsStored,
+                       ui_weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    // The fetch did not succeed so we will schedule to retry the fetch in
+    // after delaying for |kFetchRetryDelay|
+    // TODO(mcrouse): When the store is refactored from closures, the timer will
+    // be scheduled on failure of the store instead.
+    hints_fetch_timer_.Start(FROM_HERE, kFetchRetryDelay, this,
+                             &PreviewsOptimizationGuide::ScheduleHintsFetch);
+  }
+}
 
-  hint_cache_->StoreFetchedHints(std::move(get_hints_response));
+void PreviewsOptimizationGuide::OnFetchedHintsStored() {
+  hints_fetch_timer_.Stop();
+  hints_fetch_timer_.Start(
+      FROM_HERE, hint_cache_->FetchedHintsUpdateTime() - time_clock_->Now(),
+      this, &PreviewsOptimizationGuide::ScheduleHintsFetch);
 }
 
 void PreviewsOptimizationGuide::UpdateHints(
@@ -322,12 +391,62 @@
   // notification needs to be shown to the user.
 
   if (previews::params::IsHintsFetchingEnabled()) {
-    // TODO(mcrouse): On initialize, we should check if hints have been fetched
-    // recently. We will also schedule this to be called on a timer.
-    FetchHints();
+    if (ParseHintsFetchOverrideFromCommandLine()) {
+      // Skip the fetch scheduling logic and perform a hints fetch immediately
+      // after initialization.
+      last_fetch_attempt_ = time_clock_->Now();
+      FetchHints();
+    } else {
+      ScheduleHintsFetch();
+    }
   }
 }
 
+void PreviewsOptimizationGuide::ScheduleHintsFetch() {
+  DCHECK(!hints_fetch_timer_.IsRunning());
+
+  const base::TimeDelta time_until_update_time =
+      hint_cache_->FetchedHintsUpdateTime() - time_clock_->Now();
+  const base::TimeDelta time_until_retry =
+      last_fetch_attempt_ + kFetchRetryDelay - time_clock_->Now();
+  base::TimeDelta fetcher_delay;
+  if (time_until_update_time <= base::TimeDelta() &&
+      time_until_retry <= base::TimeDelta()) {
+    // Fetched hints in the store should be updated and an attempt has not been
+    // made in last |kFetchRetryDelay|.
+    last_fetch_attempt_ = time_clock_->Now();
+    hints_fetch_timer_.Start(FROM_HERE, RandomFetchDelay(), this,
+                             &PreviewsOptimizationGuide::FetchHints);
+  } else {
+    if (time_until_update_time >= base::TimeDelta()) {
+      // If the fetched hints in the store are still up-to-date, set a timer for
+      // when the hints need to be updated.
+      fetcher_delay = time_until_update_time;
+    } else {
+      // Otherwise, hints need to be updated but an attempt was made in last
+      // |kFetchRetryDelay|. Schedule the timer for after the retry
+      // delay.
+      fetcher_delay = time_until_retry;
+    }
+    hints_fetch_timer_.Start(FROM_HERE, fetcher_delay, this,
+                             &PreviewsOptimizationGuide::ScheduleHintsFetch);
+  }
+}
+
+void PreviewsOptimizationGuide::SetTimeClockForTesting(
+    const base::Clock* time_clock) {
+  time_clock_ = time_clock;
+}
+
+void PreviewsOptimizationGuide::SetHintsFetcherForTesting(
+    std::unique_ptr<previews::HintsFetcher> hints_fetcher) {
+  hints_fetcher_ = std::move(hints_fetcher);
+}
+
+HintsFetcher* PreviewsOptimizationGuide::GetHintsFetcherForTesting() {
+  return hints_fetcher_.get();
+}
+
 void PreviewsOptimizationGuide::ListenForNextUpdateForTesting(
     base::OnceClosure next_update_closure) {
   DCHECK(next_update_closure_.is_null())
diff --git a/components/previews/content/previews_optimization_guide.h b/components/previews/content/previews_optimization_guide.h
index 5a2d763..44fde91 100644
--- a/components/previews/content/previews_optimization_guide.h
+++ b/components/previews/content/previews_optimization_guide.h
@@ -16,6 +16,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
+#include "base/time/clock.h"
+#include "base/timer/timer.h"
 #include "components/optimization_guide/optimization_guide_service_observer.h"
 #include "components/previews/content/hint_cache.h"
 #include "components/previews/core/previews_experiments.h"
@@ -34,6 +36,7 @@
 class Hint;
 }  // namespace proto
 }  // namespace optimization_guide
+
 namespace previews {
 
 class HintsFetcher;
@@ -51,6 +54,7 @@
   PreviewsOptimizationGuide(
       optimization_guide::OptimizationGuideService* optimization_guide_service,
       const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
+      const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
       const base::FilePath& profile_path,
       PreviewsTopHostProvider* previews_top_host_provider,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
@@ -110,19 +114,42 @@
 
   bool has_hints() const { return !!hints_; }
 
+  // Set |time_clock_| for testing.
+  void SetTimeClockForTesting(const base::Clock* time_clock);
+
+  // Set |hints_fetcher_| for testing.
+  void SetHintsFetcherForTesting(
+      std::unique_ptr<previews::HintsFetcher> hints_fetcher);
+
+  HintsFetcher* GetHintsFetcherForTesting();
+
+  // Called when the hints store is initialized to determine when hints
+  // should be fetched and schedules the |hints_fetch_timer_| to fire based on:
+  // 1. The update time for the fetched hints in the store and
+  // 2. The last time a fetch attempt was made, |last_fetch_attempt_|.
+  // TODONOW(mcrouse) : confirm is this is ok or not.
+  void ScheduleHintsFetch();
+
+ protected:
+  // Callback executed after remote hints have been fetched and returned from
+  // the remote Optimization Guide Service. At this point, the hints response
+  // is ready to be processed and stored for use. Virtual to be mocked in
+  // testing.
+  virtual void OnHintsFetched(
+      base::Optional<
+          std::unique_ptr<optimization_guide::proto::GetHintsResponse>>
+          get_hints_response);
+
+  // Callback executed after the Hints have been successfully stored in the
+  // store. Virtual to be mocked in tests.
+  virtual void OnFetchedHintsStored();
+
  private:
   // Callback run after the hint cache is fully initialized. At this point, the
   // PreviewsOptimizationGuide is ready to process components from the
   // OptimizationGuideService and registers as an observer with it.
   void OnHintCacheInitialized();
 
-  // Callback executed after remote hints have been fetched and returned from
-  // the remote Optimization Guide Service. At this point, the hints response
-  // is ready to be processed and stored for use.
-  void OnHintsFetched(
-      std::unique_ptr<optimization_guide::proto::GetHintsResponse>
-          get_hints_response);
-
   // Called when the hints have been fully updated with the latest hints from
   // the Component Updater. This is used as a signal during tests.
   // |update_closure| is called immediately if not null.
@@ -162,9 +189,18 @@
   // Optimization Guide Service.
   std::unique_ptr<HintsFetcher> hints_fetcher_;
 
+  // Timer to schedule when to fetch hints from the remote Optimization Guide
+  // Service.
+  base::OneShotTimer hints_fetch_timer_;
+
   // TopHostProvider that this guide can query. Not owned.
   PreviewsTopHostProvider* previews_top_host_provider_ = nullptr;
 
+  // Clock used for scheduling the |hints_fetch_timer_|.
+  const base::Clock* time_clock_;
+
+  base::Time last_fetch_attempt_;
+
   // Used for fetching Hints by the Hints Fetcher.
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
 
diff --git a/components/previews/content/previews_optimization_guide_unittest.cc b/components/previews/content/previews_optimization_guide_unittest.cc
index 1b7fbda..11d21eae 100644
--- a/components/previews/content/previews_optimization_guide_unittest.cc
+++ b/components/previews/content/previews_optimization_guide_unittest.cc
@@ -21,9 +21,11 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/test/simple_test_clock.h"
 #include "components/optimization_guide/hints_component_info.h"
 #include "components/optimization_guide/optimization_guide_service.h"
 #include "components/optimization_guide/proto/hints.pb.h"
+#include "components/previews/content/hints_fetcher.h"
 #include "components/previews/content/previews_hints.h"
 #include "components/previews/content/previews_top_host_provider.h"
 #include "components/previews/content/previews_user_data.h"
@@ -43,6 +45,19 @@
 namespace {
 // A fake default page_id for testing.
 const uint64_t kDefaultPageId = 123456;
+
+enum class HintsFetcherEndState {
+  kFetchFailed = 0,
+  kFetchSuccessWithHints = 1,
+  kFetchSuccessWithNoHints = 2,
+};
+
+// Retry delay is 16 minutes to allow for kFetchRetryDelaySecs +
+// kFetchRandomMaxDelaySecs to pass.
+constexpr int kTestFetchRetryDelaySecs = 60 * 16;
+
+constexpr int kUpdateFetchHintsTimeSecs = 24 * 60 * 60;  // 24 hours.
+
 }  // namespace
 
 class TestOptimizationGuideService
@@ -78,9 +93,106 @@
   MOCK_CONST_METHOD1(GetTopHosts, std::vector<std::string>(size_t max_sites));
 };
 
+std::unique_ptr<optimization_guide::proto::GetHintsResponse> BuildHintsResponse(
+    std::vector<std::string> hosts) {
+  std::unique_ptr<optimization_guide::proto::GetHintsResponse>
+      get_hints_response =
+          std::make_unique<optimization_guide::proto::GetHintsResponse>();
+
+  for (const auto& host : hosts) {
+    optimization_guide::proto::Hint* hint = get_hints_response->add_hints();
+    hint->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+    hint->set_key(host);
+    optimization_guide::proto::PageHint* page_hint = hint->add_page_hints();
+    page_hint->set_page_pattern("page pattern");
+  }
+  return get_hints_response;
+}
+
+// A mock class implementation of HintsFetcher for unittesting
+// previews_optimization_guide.
+class TestHintsFetcher : public HintsFetcher {
+  using HintsFetchedCallback = base::OnceCallback<void(
+      base::Optional<
+          std::unique_ptr<optimization_guide::proto::GetHintsResponse>>)>;
+  using HintsFetcher::FetchOptimizationGuideServiceHints;
+
+ public:
+  TestHintsFetcher(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      GURL optimization_guide_service_url,
+      HintsFetcherEndState fetch_state)
+      : HintsFetcher(url_loader_factory, optimization_guide_service_url),
+        fetch_state_(fetch_state) {}
+
+  bool FetchOptimizationGuideServiceHints(
+      const std::vector<std::string>& hosts,
+      HintsFetchedCallback hints_fetched_callback) override {
+    switch (fetch_state_) {
+      case HintsFetcherEndState::kFetchFailed:
+        std::move(hints_fetched_callback).Run(base::nullopt);
+        return false;
+      case HintsFetcherEndState::kFetchSuccessWithHints:
+        hints_fetched_ = true;
+        std::move(hints_fetched_callback).Run(BuildHintsResponse({"host.com"}));
+        return true;
+      case HintsFetcherEndState::kFetchSuccessWithNoHints:
+        hints_fetched_ = true;
+        std::move(hints_fetched_callback).Run(BuildHintsResponse({}));
+        return true;
+    }
+    return true;
+  }
+
+  bool hints_fetched() { return hints_fetched_; }
+
+ private:
+  bool hints_fetched_ = false;
+  HintsFetcherEndState fetch_state_;
+};
+
+// A Test PreviewsOptimizationGuide to observe and record when callbacks
+// from hints fetching and storing occur.
+class TestPreviewsOptimizationGuide : public PreviewsOptimizationGuide {
+ public:
+  TestPreviewsOptimizationGuide(
+      optimization_guide::OptimizationGuideService* optimization_guide_service,
+      const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
+      const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
+      const base::FilePath& profile_path,
+      PreviewsTopHostProvider* previews_top_host_provider,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+      : PreviewsOptimizationGuide(optimization_guide_service,
+                                  ui_task_runner,
+                                  background_task_runner,
+                                  profile_path,
+                                  previews_top_host_provider,
+                                  url_loader_factory) {}
+
+  bool fetched_hints_stored() { return fetched_hints_stored_; }
+
+ private:
+  void OnHintsFetched(
+      base::Optional<
+          std::unique_ptr<optimization_guide::proto::GetHintsResponse>>
+          get_hints_response) override {
+    fetched_hints_stored_ = false;
+    PreviewsOptimizationGuide::OnHintsFetched(std::move(get_hints_response));
+  }
+
+  void OnFetchedHintsStored() override {
+    fetched_hints_stored_ = true;
+    PreviewsOptimizationGuide::OnFetchedHintsStored();
+  }
+
+  bool fetched_hints_stored_ = false;
+};
+
 class PreviewsOptimizationGuideTest : public testing::Test {
  public:
-  PreviewsOptimizationGuideTest() {}
+  PreviewsOptimizationGuideTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME) {}
 
   ~PreviewsOptimizationGuideTest() override {}
 
@@ -102,6 +214,14 @@
     return optimization_guide_service_.get();
   }
 
+  network::SharedURLLoaderFactory* url_loader_factory() {
+    return url_loader_factory_.get();
+  }
+
+  TestHintsFetcher* hints_fetcher() {
+    return static_cast<TestHintsFetcher*>(guide_->GetHintsFetcherForTesting());
+  }
+
   void ProcessHints(const optimization_guide::proto::Configuration& config,
                     const std::string& version) {
     optimization_guide::HintsComponentInfo info(
@@ -126,11 +246,17 @@
     optimization_guide_service_ =
         std::make_unique<TestOptimizationGuideService>(
             scoped_task_environment_.GetMainThreadTaskRunner());
-    guide_ = std::make_unique<PreviewsOptimizationGuide>(
+    guide_ = std::make_unique<TestPreviewsOptimizationGuide>(
         optimization_guide_service_.get(),
+        scoped_task_environment_.GetMainThreadTaskRunner(),
         scoped_task_environment_.GetMainThreadTaskRunner(), temp_dir(),
         previews_top_host_provider_.get(), url_loader_factory_);
 
+    guide_->SetTimeClockForTesting(scoped_task_environment_.GetMockClock());
+
+    base::test::ScopedFeatureList scoped_list;
+    scoped_list.InitAndEnableFeature(features::kOptimizationHintsFetching);
+
     // Add observer is called after the HintCache is fully initialized,
     // indicating that the PreviewsOptimizationGuide is ready to process hints.
     while (!optimization_guide_service_->AddObserverCalled()) {
@@ -143,9 +269,24 @@
     RunUntilIdle();
   }
 
+  std::unique_ptr<TestHintsFetcher> BuildTestHintsFetcher(
+      HintsFetcherEndState end_state) {
+    std::unique_ptr<TestHintsFetcher> hints_fetcher =
+        std::make_unique<TestHintsFetcher>(
+            url_loader_factory_, GURL("https://hintsserver.com"), end_state);
+    return hints_fetcher;
+  }
+
   base::FilePath temp_dir() const { return temp_dir_.GetPath(); }
 
  protected:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  void MoveClockForwardBy(base::TimeDelta time_delta) {
+    scoped_task_environment_.FastForwardBy(time_delta);
+    base::RunLoop().RunUntilIdle();
+  }
+
   void RunUntilIdle() {
     scoped_task_environment_.RunUntilIdle();
     base::RunLoop().RunUntilIdle();
@@ -183,6 +324,11 @@
       PreviewsType type,
       net::EffectiveConnectionType* out_ect_threshold);
 
+  void RunUntilFetchedHintsStored() {
+    while (!guide_->fetched_hints_stored()) {
+    }
+  }
+
  private:
   void WriteConfigToFile(const optimization_guide::proto::Configuration& config,
                          const base::FilePath& filePath) {
@@ -193,14 +339,19 @@
                               serialized_config.length()));
   }
 
+  void TestOnHintsFetched(
+      std::unique_ptr<optimization_guide::proto::GetHintsResponse>
+          get_hints_response) {}
+
+  std::unique_ptr<TestHintsFetcher> test_fetcher_;
   // Callback used to indicate that the asynchronous call to
   // MaybeLoadOptimizationHints() has completed its processing.
   void OnLoadOptimizationHints();
 
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
   base::ScopedTempDir temp_dir_;
 
-  std::unique_ptr<PreviewsOptimizationGuide> guide_;
+  // std::unique_ptr<PreviewsOptimizationGuide> guide_;
+  std::unique_ptr<TestPreviewsOptimizationGuide> guide_;
   std::unique_ptr<TestOptimizationGuideService> optimization_guide_service_;
   std::unique_ptr<MockPreviewsTopHostProvider> previews_top_host_provider_;
 
@@ -213,6 +364,7 @@
 
   GURL loaded_hints_document_gurl_;
   std::vector<std::string> loaded_hints_resource_patterns_;
+  // const base::SimpleTestClock* test_clock_;
 
   DISALLOW_COPY_AND_ASSIGN(PreviewsOptimizationGuideTest);
 };
@@ -1575,18 +1727,110 @@
   EXPECT_TRUE(optimization_guide_service()->RemoveObserverCalled());
 }
 
-TEST_F(PreviewsOptimizationGuideTest, HintsFetcherEnabled) {
+TEST_F(PreviewsOptimizationGuideTest, HintsFetcherEnabledNoHosts) {
+  base::HistogramTester histogram_tester;
   base::test::ScopedFeatureList scoped_list;
   scoped_list.InitAndEnableFeature(features::kOptimizationHintsFetching);
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   command_line->AppendSwitchASCII("optimization_guide_service_url",
                                   "https://hintsserver.com");
 
-  EXPECT_CALL(*top_host_provider(), GetTopHosts(testing::_));
-  CreateServiceAndGuide();
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchSuccessWithHints));
+
+  EXPECT_CALL(*top_host_provider(), GetTopHosts(testing::_)).Times(1);
+
   // Load hints so that OnHintsUpdated is called. This will force FetchHints to
   // be triggered if OptimizationHintsFetching is enabled.
   InitializeFixedCountResourceLoadingHints();
+  // Cause the timer to fire the fetch event.
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_FALSE(hints_fetcher()->hints_fetched());
+}
+
+TEST_F(PreviewsOptimizationGuideTest, HintsFetcherEnabledWithHosts) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList scoped_list;
+  scoped_list.InitAndEnableFeature(features::kOptimizationHintsFetching);
+  std::string opt_guide_url = "https://hintsserver.com";
+
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchSuccessWithHints));
+
+  std::vector<std::string> hosts = {"example1.com", "example2.com"};
+  EXPECT_CALL(*top_host_provider(), GetTopHosts(testing::_))
+      .Times(1)
+      .WillRepeatedly(testing::Return(hosts));
+
+  // Load hints so that OnHintsUpdated is called. This will force FetchHints to
+  // be triggered if OptimizationHintsFetching is enabled.
+  InitializeFixedCountResourceLoadingHints();
+
+  // Force timer to expire and schedule a hints fetch.
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_TRUE(hints_fetcher()->hints_fetched());
+}
+
+TEST_F(PreviewsOptimizationGuideTest, HintsFetcherTimerRetryDelay) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList scoped_list;
+  scoped_list.InitAndEnableFeature(features::kOptimizationHintsFetching);
+  std::string opt_guide_url = "https://hintsserver.com";
+
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchFailed));
+
+  std::vector<std::string> hosts = {"example1.com", "example2.com"};
+  EXPECT_CALL(*top_host_provider(), GetTopHosts(testing::_))
+      .Times(2)
+      .WillRepeatedly(testing::Return(hosts));
+
+  // Force hints fetch scheduling.
+  guide()->ScheduleHintsFetch();
+
+  // Force timer to expire and schedule a hints fetch - first time.
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_FALSE(hints_fetcher()->hints_fetched());
+
+  // Force speculative timer to expire after fetch fails first time, update
+  // hints fetcher so it succeeds this time.
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchSuccessWithHints));
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_TRUE(hints_fetcher()->hints_fetched());
+}
+
+TEST_F(PreviewsOptimizationGuideTest, HintsFetcherTimerFetchSucceeds) {
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList scoped_list;
+  scoped_list.InitAndEnableFeature(features::kOptimizationHintsFetching);
+  std::string opt_guide_url = "https://hintsserver.com";
+
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchSuccessWithHints));
+
+  std::vector<std::string> hosts = {"example1.com", "example2.com"};
+  EXPECT_CALL(*top_host_provider(), GetTopHosts(testing::_))
+      .WillRepeatedly(testing::Return(hosts));
+
+  // Force hints fetch scheduling.
+  guide()->ScheduleHintsFetch();
+
+  // Force timer to expire and schedule a hints fetch that succeeds.
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_TRUE(hints_fetcher()->hints_fetched());
+
+  // TODO(mcrouse): Make sure timer is triggered by metadata entry,
+  // |hint_cache| control needed.
+  guide()->SetHintsFetcherForTesting(
+      BuildTestHintsFetcher(HintsFetcherEndState::kFetchSuccessWithHints));
+
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kTestFetchRetryDelaySecs));
+  EXPECT_FALSE(hints_fetcher()->hints_fetched());
+  MoveClockForwardBy(base::TimeDelta::FromSeconds(kUpdateFetchHintsTimeSecs));
+
+  RunUntilFetchedHintsStored();
+  EXPECT_TRUE(hints_fetcher()->hints_fetched());
 }
 
 TEST_F(PreviewsOptimizationGuideTest, HintsFetcherDisabled) {
diff --git a/components/previews/content/proto/hint_cache.proto b/components/previews/content/proto/hint_cache.proto
index 9f795aa8..b04fc3c 100644
--- a/components/previews/content/proto/hint_cache.proto
+++ b/components/previews/content/proto/hint_cache.proto
@@ -19,4 +19,7 @@
   optional string version = 1;
   // The actual hint data.
   optional optimization_guide.proto.Hint hint = 2;
+  // Time when top host fetched hints are still usable but update should
+  // be requested. This is set on the fetched metadata entry.
+  optional int64 update_time_secs = 3;
 }
diff --git a/components/previews/core/previews_switches.cc b/components/previews/core/previews_switches.cc
index 676be59..3906dd8 100644
--- a/components/previews/core/previews_switches.cc
+++ b/components/previews/core/previews_switches.cc
@@ -33,6 +33,11 @@
 // valid value to this switch causes Chrome startup to block on hints parsing.
 const char kHintsProtoOverride[] = "optimization_guide_hints_override";
 
+// Overrides scheduling and time delays for fetching hints and causes a hints
+// fetch immediately on start up using the provided comma separate lists of
+// hosts.
+const char kFetchHintsOverride[] = "optimization-guide-fetch-hints-override";
+
 // Overrides the Optimization Guide Service URL that the HintsFetcher will
 // request remote hints from.
 const char kOptimizationGuideServiceURL[] = "optimization_guide_service_url";
diff --git a/components/previews/core/previews_switches.h b/components/previews/core/previews_switches.h
index 4fa8f396..9fa115f 100644
--- a/components/previews/core/previews_switches.h
+++ b/components/previews/core/previews_switches.h
@@ -14,6 +14,7 @@
 extern const char kIgnoreLitePageRedirectOptimizationBlacklist[];
 extern const char kClearLitePageRedirectLocalBlacklist[];
 extern const char kHintsProtoOverride[];
+extern const char kFetchHintsOverride[];
 extern const char kOptimizationGuideServiceURL[];
 extern const char kOptimizationGuideServiceAPIKey[];
 extern const char kPurgeHintCacheStore[];
diff --git a/components/sync/model/fake_model_type_sync_bridge.cc b/components/sync/model/fake_model_type_sync_bridge.cc
index 58a22ef..4ead1c4 100644
--- a/components/sync/model/fake_model_type_sync_bridge.cc
+++ b/components/sync/model/fake_model_type_sync_bridge.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "components/sync/base/hash_util.h"
 #include "components/sync/model/conflict_resolution.h"
 #include "components/sync/model/model_type_store.h"
@@ -131,6 +132,7 @@
 
 const EntityData& FakeModelTypeSyncBridge::Store::GetData(
     const std::string& key) const {
+  DCHECK(data_store_.count(key) != 0) << " for key " << key;
   return *data_store_.find(key)->second;
 }
 
@@ -227,14 +229,16 @@
     std::string storage_key = change->storage_key();
     EXPECT_NE(SupportsGetStorageKey(), storage_key.empty());
     if (storage_key.empty()) {
-      storage_key = GetStorageKeyImpl(change->data());
-      if (base::ContainsKey(keys_to_ignore_, storage_key)) {
+      if (base::ContainsKey(values_to_ignore_,
+                            change->data().specifics.preference().value())) {
         change_processor()->UntrackEntityForClientTagHash(
             change->data().client_tag_hash);
-      } else {
-        change_processor()->UpdateStorageKey(change->data(), storage_key,
-                                             metadata_change_list.get());
+        continue;
       }
+
+      storage_key = GenerateStorageKey(change->data());
+      change_processor()->UpdateStorageKey(change->data(), storage_key,
+                                           metadata_change_list.get());
     }
     remote_storage_keys.insert(storage_key);
     db_->PutData(storage_key, change->data());
@@ -266,7 +270,7 @@
         std::string storage_key = change->storage_key();
         EXPECT_NE(SupportsGetStorageKey(), storage_key.empty());
         if (storage_key.empty()) {
-          storage_key = GetStorageKeyImpl(change->data());
+          storage_key = GenerateStorageKey(change->data());
           change_processor()->UpdateStorageKey(change->data(), storage_key,
                                                metadata_changes.get());
         }
@@ -338,23 +342,22 @@
 std::string FakeModelTypeSyncBridge::GetStorageKey(
     const EntityData& entity_data) {
   DCHECK(supports_get_storage_key_);
-  return GetStorageKeyImpl(entity_data);
+  return GenerateStorageKey(entity_data);
 }
 
-std::string FakeModelTypeSyncBridge::GetStorageKeyImpl(
+std::string FakeModelTypeSyncBridge::GenerateStorageKey(
     const EntityData& entity_data) {
-  return entity_data.specifics.preference().name();
+  if (supports_get_storage_key_) {
+    return entity_data.specifics.preference().name();
+  } else {
+    return base::NumberToString(++last_generated_storage_key_);
+  }
 }
 
 bool FakeModelTypeSyncBridge::SupportsGetStorageKey() const {
   return supports_get_storage_key_;
 }
 
-void FakeModelTypeSyncBridge::SetSupportsGetStorageKey(
-    bool supports_get_storage_key) {
-  supports_get_storage_key_ = supports_get_storage_key;
-}
-
 ConflictResolution FakeModelTypeSyncBridge::ResolveConflict(
     const EntityData& local_data,
     const EntityData& remote_data) const {
@@ -391,8 +394,19 @@
   return new_data;
 }
 
-void FakeModelTypeSyncBridge::SetKeyToIgnore(const std::string key) {
-  keys_to_ignore_.insert(key);
+void FakeModelTypeSyncBridge::SetSupportsGetStorageKey(
+    bool supports_get_storage_key) {
+  supports_get_storage_key_ = supports_get_storage_key;
+}
+
+std::string FakeModelTypeSyncBridge::GetLastGeneratedStorageKey() const {
+  // Verify that GenerateStorageKey() was called at least once.
+  EXPECT_NE(0, last_generated_storage_key_);
+  return base::NumberToString(last_generated_storage_key_);
+}
+
+void FakeModelTypeSyncBridge::AddValueToIgnore(const std::string& value) {
+  values_to_ignore_.insert(value);
 }
 
 }  // namespace syncer
diff --git a/components/sync/model/fake_model_type_sync_bridge.h b/components/sync/model/fake_model_type_sync_bridge.h
index 82e37fa..dc783b80 100644
--- a/components/sync/model/fake_model_type_sync_bridge.h
+++ b/components/sync/model/fake_model_type_sync_bridge.h
@@ -122,7 +122,6 @@
   std::string GetClientTag(const EntityData& entity_data) override;
   std::string GetStorageKey(const EntityData& entity_data) override;
   bool SupportsGetStorageKey() const override;
-  void SetSupportsGetStorageKey(bool supports_get_storage_key);
   ConflictResolution ResolveConflict(
       const EntityData& local_data,
       const EntityData& remote_data) const override;
@@ -141,8 +140,20 @@
   // test code here, this function is needed to manually copy it.
   static std::unique_ptr<EntityData> CopyEntityData(const EntityData& old_data);
 
-  // Sets storage key which will be ignored by bridge.
-  void SetKeyToIgnore(const std::string key);
+  // Influences the way the bridge produces storage key. If set to true, the
+  // bridge will compute a storage key deterministically from specifics, via
+  // GetStorageKey(). If set to false, it will return autoincrement-like storage
+  // keys that cannot be inferred from specifics, and exercise
+  // UpdateStorageKey() for remote changes to report storage keys.
+  void SetSupportsGetStorageKey(bool supports_get_storage_key);
+
+  // Returns the last generated autoincrement-like storage key, applicable only
+  // for the SetSupportsGetStorageKey(false) case (otherwise the storage key
+  // gets inferred deterministically from specifics).
+  std::string GetLastGeneratedStorageKey() const;
+
+  // Add values that will be ignored by bridge.
+  void AddValueToIgnore(const std::string& value);
 
   const Store& db() const { return *db_; }
   Store* mutable_db() { return db_.get(); }
@@ -155,13 +166,13 @@
   // Applies |change_list| to the metadata store.
   void ApplyMetadataChangeList(std::unique_ptr<MetadataChangeList> change_list);
 
-  std::string GetStorageKeyImpl(const EntityData& entity_data);
+  std::string GenerateStorageKey(const EntityData& entity_data);
 
   // The conflict resolution to use for calls to ResolveConflict.
   std::unique_ptr<ConflictResolution> conflict_resolution_;
 
-  // The storage keys which bridge will ignore.
-  std::unordered_set<std::string> keys_to_ignore_;
+  // The keys that the bridge will ignore.
+  std::unordered_set<std::string> values_to_ignore_;
 
   // Whether an error should be produced on the next bridge call.
   bool error_next_ = false;
@@ -170,6 +181,11 @@
   // responsible for calling UpdateStorageKey when processing new entities in
   // MergeSyncData/ApplySyncChanges.
   bool supports_get_storage_key_ = true;
+
+  // Last dynamically-generated storage key, for the case where
+  // |supports_get_storage_key_| == false (otherwise the storage key gets
+  // inferred deterministically from specifics).
+  int last_generated_storage_key_ = 0;
 };
 
 }  // namespace syncer
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.cc b/components/sync/model_impl/client_tag_based_model_type_processor.cc
index 3dab9253..843ebd85 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.cc
@@ -721,7 +721,8 @@
 
 ProcessorEntity* ClientTagBasedModelTypeProcessor::ProcessUpdate(
     std::unique_ptr<UpdateResponseData> update,
-    EntityChangeList* entity_changes) {
+    EntityChangeList* entity_changes,
+    std::string* storage_key_to_clear) {
   const EntityData& data = *update->entity;
   const std::string& client_tag_hash = data.client_tag_hash;
 
@@ -767,7 +768,8 @@
   ConflictResolution::Type resolution_type = ConflictResolution::TYPE_SIZE;
   if (entity && entity->IsUnsynced()) {
     // Handle conflict resolution.
-    resolution_type = ResolveConflict(*update, entity, entity_changes);
+    resolution_type =
+        ResolveConflict(*update, entity, entity_changes, storage_key_to_clear);
     UMA_HISTOGRAM_ENUMERATION("Sync.ResolveConflict", resolution_type,
                               ConflictResolution::TYPE_SIZE);
   } else {
@@ -830,7 +832,8 @@
 ConflictResolution::Type ClientTagBasedModelTypeProcessor::ResolveConflict(
     const UpdateResponseData& update,
     ProcessorEntity* entity,
-    EntityChangeList* changes) {
+    EntityChangeList* changes,
+    std::string* storage_key_to_clear) {
   const EntityData& remote_data = *update.entity;
 
   ConflictResolution::Type resolution_type = ConflictResolution::TYPE_SIZE;
@@ -840,6 +843,9 @@
   if (entity->MatchesData(remote_data)) {
     // The changes are identical so there isn't a real conflict.
     resolution_type = ConflictResolution::CHANGES_MATCH;
+  } else if (entity->metadata().is_deleted()) {
+    // Local tombstone vs remote update (non-deletion). Should be undeleted.
+    resolution_type = ConflictResolution::USE_REMOTE;
   } else if (entity->MatchesOwnBaseData()) {
     // If there is no real local change, then the entity must be unsynced due to
     // a pending local re-encryption request. In this case, the remote data
@@ -876,17 +882,28 @@
       break;
     case ConflictResolution::USE_REMOTE:
     case ConflictResolution::IGNORE_LOCAL_ENCRYPTION:
-      // Squash the pending commit.
-      entity->RecordForcedUpdate(update);
       // Update client data to match server.
       if (update.entity->is_deleted()) {
+        DCHECK(!entity->metadata().is_deleted());
         changes->push_back(EntityChange::CreateDelete(entity->storage_key()));
-      } else {
+      } else if (!entity->metadata().is_deleted()) {
         changes->push_back(EntityChange::CreateUpdate(entity->storage_key(),
                                                       update.entity->Clone()));
+      } else {
+        // Remote undeletion. This could imply a new storage key for some
+        // bridges, so we may need to wait until UpdateStorageKey() is called.
+        if (!bridge_->SupportsGetStorageKey()) {
+          *storage_key_to_clear = entity->storage_key();
+          entity->ClearStorageKey();
+        }
+        changes->push_back(EntityChange::CreateAdd(entity->storage_key(),
+                                                   update.entity->Clone()));
       }
+      // Squash the pending commit.
+      entity->RecordForcedUpdate(update);
       break;
     case ConflictResolution::USE_NEW:
+      DCHECK(!entity->metadata().is_deleted());
       // Record that we received the update.
       entity->RecordIgnoredUpdate(update);
       // Make a new pending commit to update the server.
@@ -1060,7 +1077,9 @@
 
   for (std::unique_ptr<syncer::UpdateResponseData>& update : updates) {
     DCHECK(update);
-    ProcessorEntity* entity = ProcessUpdate(std::move(update), &entity_changes);
+    std::string storage_key_to_clear;
+    ProcessorEntity* entity = ProcessUpdate(std::move(update), &entity_changes,
+                                            &storage_key_to_clear);
 
     if (!entity) {
       // The update is either of the following:
@@ -1079,8 +1098,19 @@
     if (entity->storage_key().empty()) {
       // Storage key of this entity is not known yet. Don't update metadata, it
       // will be done from UpdateStorageKey.
+
+      // If this is the result of a conflict resolution (where a remote
+      // undeletion was preferred), then need to clear a metadata entry from
+      // the database.
+      if (!storage_key_to_clear.empty()) {
+        metadata_changes->ClearMetadata(storage_key_to_clear);
+        storage_key_to_tag_hash_.erase(storage_key_to_clear);
+      }
       continue;
     }
+
+    DCHECK(storage_key_to_clear.empty());
+
     if (entity->CanClearMetadata()) {
       metadata_changes->ClearMetadata(entity->storage_key());
       storage_key_to_tag_hash_.erase(entity->storage_key());
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.h b/components/sync/model_impl/client_tag_based_model_type_processor.h
index 0c7fbb7..e71c767 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.h
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.h
@@ -131,13 +131,18 @@
   // Helper function to process the update for a single entity. If a local data
   // change is required, it will be added to |entity_changes|. The return value
   // is the tracked entity, or nullptr if the update should be ignored.
+  // |storage_key_to_clear| must not be null and allows the implementation to
+  // indicate that a certain storage key is now obsolete and should be cleared,
+  // which is leveraged in certain conflict resolution scenarios.
   ProcessorEntity* ProcessUpdate(std::unique_ptr<UpdateResponseData> update,
-                                 EntityChangeList* entity_changes);
+                                 EntityChangeList* entity_changes,
+                                 std::string* storage_key_to_clear);
 
   // Resolve a conflict between |update| and the pending commit in |entity|.
   ConflictResolution::Type ResolveConflict(const UpdateResponseData& update,
                                            ProcessorEntity* entity,
-                                           EntityChangeList* changes);
+                                           EntityChangeList* changes,
+                                           std::string* storage_key_to_clear);
 
   // Recommit all entities for encryption except those in |already_updated|.
   void RecommitAllForEncryption(
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc b/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
index dd4e2cb..deebe2d 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor_unittest.cc
@@ -1385,6 +1385,71 @@
 }
 
 TEST_F(ClientTagBasedModelTypeProcessorTest,
+       ShouldResolveConflictToRemoteUndeletion) {
+  InitializeToReadyState();
+  WriteItemAndAck(kKey1, kValue1);
+  ASSERT_EQ(0U, worker()->GetNumPendingCommits());
+
+  bridge()->DeleteItem(kKey1);
+  ASSERT_EQ(1U, worker()->GetNumPendingCommits());
+  worker()->VerifyPendingCommits({{kHash1}});
+  ASSERT_TRUE(worker()->GetLatestPendingCommitForHash(kHash1));
+  ASSERT_TRUE(
+      worker()->GetLatestPendingCommitForHash(kHash1)->entity->is_deleted());
+  ASSERT_EQ(2U, db()->data_change_count());
+  ASSERT_EQ(3U, db()->metadata_change_count());
+  ASSERT_TRUE(type_processor()->IsTrackingEntityForTest(kKey1));
+
+  worker()->UpdateFromServer(kHash1, GenerateSpecifics(kKey1, kValue2));
+
+  // Updated client data and metadata; no new commit request.
+  EXPECT_TRUE(type_processor()->IsTrackingEntityForTest(kKey1));
+  EXPECT_EQ(3U, db()->data_change_count());
+  EXPECT_EQ(kValue2, db()->GetValue(kKey1));
+  EXPECT_EQ(4U, db()->metadata_change_count());
+  EXPECT_EQ(2, db()->GetMetadata(kKey1).server_version());
+  worker()->VerifyPendingCommits({{kHash1}});
+}
+
+TEST_F(ClientTagBasedModelTypeProcessorTest,
+       ShouldResolveConflictToRemoteUndeletionWithUpdateStorageKey) {
+  bridge()->SetSupportsGetStorageKey(false);
+  InitializeToReadyState();
+  WriteItemAndAck(kKey1, kValue1);
+  ASSERT_EQ(0U, worker()->GetNumPendingCommits());
+
+  bridge()->DeleteItem(kKey1);
+  ASSERT_EQ(1U, worker()->GetNumPendingCommits());
+  worker()->VerifyPendingCommits({{kHash1}});
+  ASSERT_TRUE(worker()->GetLatestPendingCommitForHash(kHash1));
+  ASSERT_TRUE(
+      worker()->GetLatestPendingCommitForHash(kHash1)->entity->is_deleted());
+  ASSERT_EQ(2U, db()->data_change_count());
+  ASSERT_EQ(3U, db()->metadata_change_count());
+  ASSERT_TRUE(type_processor()->IsTrackingEntityForTest(kKey1));
+
+  worker()->UpdateFromServer(kHash1, GenerateSpecifics(kKey1, kValue2));
+
+  // A new storage key should have been generated, which should replace the
+  // previous when it comes to storing data and metadata.
+  const std::string new_storage_key = bridge()->GetLastGeneratedStorageKey();
+  ASSERT_NE(kKey1, new_storage_key);
+  EXPECT_TRUE(db()->HasData(new_storage_key));
+  EXPECT_TRUE(db()->HasMetadata(new_storage_key));
+  EXPECT_TRUE(type_processor()->IsTrackingEntityForTest(new_storage_key));
+  EXPECT_FALSE(db()->HasData(kKey1));
+  EXPECT_FALSE(db()->HasMetadata(kKey1));
+  EXPECT_FALSE(type_processor()->IsTrackingEntityForTest(kKey1));
+
+  // Updated client data and metadata; no new commit request.
+  EXPECT_EQ(3U, db()->data_change_count());
+  EXPECT_EQ(kValue2, db()->GetValue(new_storage_key));
+  EXPECT_EQ(5U, db()->metadata_change_count());
+  EXPECT_EQ(2, db()->GetMetadata(new_storage_key).server_version());
+  worker()->VerifyPendingCommits({{kHash1}});
+}
+
+TEST_F(ClientTagBasedModelTypeProcessorTest,
        ShouldResolveConflictToRemoteVersion) {
   InitializeToReadyState();
   bridge()->WriteItem(kKey1, kValue1);
@@ -1992,19 +2057,21 @@
   // Create update which will be ignored by bridge.
   updates.push_back(
       worker()->GenerateUpdateData(kHash3, GenerateSpecifics(kKey3, kValue3)));
-  bridge()->SetKeyToIgnore(kKey3);
+  bridge()->AddValueToIgnore(kValue3);
   worker()->UpdateFromServer(std::move(updates));
   EXPECT_EQ(1, bridge()->merge_call_count());
   EXPECT_EQ(1U, ProcessorEntityCount());
-  // Metadata should be written under kKey1. This means that UpdateStorageKey
-  // was called and value of storage key got propagated to MetadataChangeList.
-  EXPECT_TRUE(db()->HasMetadata(kKey1));
+  // Metadata should be written under a new storage key. This means that
+  // UpdateStorageKey was called and value of storage key got propagated to
+  // MetadataChangeList.
+  const std::string storage_key1 = bridge()->GetLastGeneratedStorageKey();
+  EXPECT_TRUE(db()->HasMetadata(storage_key1));
   EXPECT_EQ(1U, db()->metadata_count());
   EXPECT_EQ(0, bridge()->get_storage_key_call_count());
 
-  // Local update of kKey1 should affect the same entity. This ensures that
-  // storage key to client tag hash mapping was updated on the previous step.
-  bridge()->WriteItem(kKey1, kValue2);
+  // Local update should affect the same entity. This ensures that storage key
+  // to client tag hash mapping was updated on the previous step.
+  bridge()->WriteItem(storage_key1, kValue2);
   EXPECT_EQ(1U, ProcessorEntityCount());
   EXPECT_EQ(1U, db()->metadata_count());
 
@@ -2012,7 +2079,9 @@
   // It should call UpdateStorageKey, not GetStorageKey.
   worker()->UpdateFromServer(kHash2, GenerateSpecifics(kKey2, kValue2));
   EXPECT_EQ(1, bridge()->apply_call_count());
-  EXPECT_TRUE(db()->HasMetadata(kKey2));
+  const std::string storage_key2 = bridge()->GetLastGeneratedStorageKey();
+  EXPECT_NE(storage_key1, storage_key2);
+  EXPECT_TRUE(db()->HasMetadata(storage_key2));
   EXPECT_EQ(2U, db()->metadata_count());
   EXPECT_EQ(0, bridge()->get_storage_key_call_count());
 }
@@ -2041,7 +2110,7 @@
   // FakeModelTypeSyncBridge to call UpdateStorageKey for new entities and will
   // DCHECK if GetStorageKey gets called.
   bridge()->SetSupportsGetStorageKey(false);
-  bridge()->SetKeyToIgnore(kKey1);
+  bridge()->AddValueToIgnore(kValue1);
   ModelReadyToSync();
   OnSyncStarting();
 
diff --git a/components/sync/model_impl/processor_entity.cc b/components/sync/model_impl/processor_entity.cc
index 663b9ed0..81c19b2 100644
--- a/components/sync/model_impl/processor_entity.cc
+++ b/components/sync/model_impl/processor_entity.cc
@@ -80,6 +80,11 @@
   storage_key_ = storage_key;
 }
 
+void ProcessorEntity::ClearStorageKey() {
+  DCHECK(metadata_.is_deleted());
+  storage_key_.clear();
+}
+
 void ProcessorEntity::SetCommitData(std::unique_ptr<EntityData> data) {
   DCHECK(data);
   // Update data's fields from metadata.
@@ -113,10 +118,10 @@
 
 bool ProcessorEntity::MatchesOwnBaseData() const {
   DCHECK(IsUnsynced());
-  DCHECK(!metadata_.specifics_hash().empty());
   if (metadata_.is_deleted()) {
     return false;
   }
+  DCHECK(!metadata_.specifics_hash().empty());
   return metadata_.specifics_hash() == metadata_.base_specifics_hash();
 }
 
diff --git a/components/sync/model_impl/processor_entity.h b/components/sync/model_impl/processor_entity.h
index 023d00d..af4259f 100644
--- a/components/sync/model_impl/processor_entity.h
+++ b/components/sync/model_impl/processor_entity.h
@@ -111,6 +111,10 @@
   // an entity initialized with empty storage key.
   void SetStorageKey(const std::string& storage_key);
 
+  // Undoes SetStorageKey(), which is needed in certain conflict resolution
+  // scenarios.
+  void ClearStorageKey();
+
   // Takes the passed commit data updates its fields with values from metadata
   // and caches it in the instance.
   void SetCommitData(std::unique_ptr<EntityData> data);
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-25.png.sha1
index b7af035..4404c52 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-2e43d01c6c3701a096da20305e999daebebbd327
\ No newline at end of file
+b35f3cbd8f584bac45c2f8618bb827136adfd957
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-26.png.sha1
index a76c136..54fc0f8 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_bottom.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-785ed16a1915a87c5c372c551c22e7653ee92b0b
\ No newline at end of file
+4494408049df80d7edf64c1d39d0a676b348b445
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-25.png.sha1
index 87de8f3..054ff38 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-53a11c2b33f9b157820cfffbedb6ee85805eca92
\ No newline at end of file
+671050041d59f15213880407d57847784dc6688b
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-26.png.sha1
index 04f0565..9f1a972 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_left.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-6ab1ece633eb6d2b97167653cc1ca2929736a55c
\ No newline at end of file
+3fbefba834615082936b05c145d735de48225680
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-25.png.sha1
index 9977e6c..665fa23 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-cbffb42766d954725dd2af8193d75d9557ede979
\ No newline at end of file
+95097302caa8353ab8d680cbeff0a62748b4b7c0
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-26.png.sha1
index de2eb33..b84d37e2 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_right.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-b293e14a49f0f34aa6659c3f29d678c90a8f4dee
\ No newline at end of file
+135a8992a27da283499890c51343149d27f53514
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-25.png.sha1
index 340401c..fa654279 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-8690cc2c9d4f13aadc0142fa7883f7b398d88fe1
\ No newline at end of file
+f93bc767631ae5bc2b2c806283e52a2d66972820
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-26.png.sha1
index d1c6461..15959425 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_clicking_top.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-141705b3bc31eac3fb3897ebf29224d89720e087
\ No newline at end of file
+7b51ec5fec23ecb51635bf98e4e2ce6a0f053d69
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-25.png.sha1
index 9dc30f43..856c45d 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-1c816c86bdebb5b034e1fa9e76a2813d71cc1fc7
\ No newline at end of file
+bcb51742e0788962d58dfa8b7b0ff37100748b11
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-26.png.sha1
index 7e1344c..8e8af213b 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_middle.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-02880cd1a9f87d62cb20860d8a3eb0fecee54f24
\ No newline at end of file
+3474d25341d4daad894fffb515c4a6726b61d040
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-25.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-25.png.sha1
index 83e36a5..9d38513 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-25.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-25.png.sha1
@@ -1 +1 @@
-84d05e02d5dcfbf106024f5b0f65b05211b3270d
\ No newline at end of file
+83f9bc92393fe3a269e61cde586b9a45b1ab4c5f
\ No newline at end of file
diff --git a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-26.png.sha1 b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-26.png.sha1
index c3a23f13..9d15c818 100644
--- a/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-26.png.sha1
+++ b/components/test/data/vr_browser_ui/render_tests/VrBrowserNativeUiTest.suggestion_hovering_top.Pixel_XL-26.png.sha1
@@ -1 +1 @@
-6b4327781d36a9811da53c341b77016c00f6c632
\ No newline at end of file
+82f84f9ff5755656dd2a071f08391055d16d70cc
\ No newline at end of file
diff --git a/components/user_manager/fake_user_manager.cc b/components/user_manager/fake_user_manager.cc
index 45dd014a..54270c1 100644
--- a/components/user_manager/fake_user_manager.cc
+++ b/components/user_manager/fake_user_manager.cc
@@ -286,7 +286,7 @@
 }
 
 PrefService* FakeUserManager::GetLocalState() const {
-  return nullptr;
+  return local_state_;
 }
 
 bool FakeUserManager::IsEnterpriseManaged() const {
diff --git a/components/user_manager/fake_user_manager.h b/components/user_manager/fake_user_manager.h
index 4f87775..0f124f68 100644
--- a/components/user_manager/fake_user_manager.h
+++ b/components/user_manager/fake_user_manager.h
@@ -32,6 +32,8 @@
       const AccountId& account_id,
       bool is_affiliated);
 
+  void set_local_state(PrefService* local_state) { local_state_ = local_state; }
+
   // UserManager overrides.
   const user_manager::UserList& GetUsers() const override;
   user_manager::UserList GetUsersAllowedForMultiProfile() const override;
@@ -143,6 +145,9 @@
  protected:
   user_manager::User* primary_user_;
 
+  // Can be set by set_local_state().
+  PrefService* local_state_ = nullptr;
+
   // If set this is the active user. If empty, the first created user is the
   // active user.
   AccountId active_account_id_ = EmptyAccountId();
diff --git a/components/viz/client/client_resource_provider.cc b/components/viz/client/client_resource_provider.cc
index 2c59c0b..d2c3d9c 100644
--- a/components/viz/client/client_resource_provider.cc
+++ b/components/viz/client/client_resource_provider.cc
@@ -388,7 +388,7 @@
 
 ClientResourceProvider::ScopedSkSurface::~ScopedSkSurface() {
   if (surface_)
-    surface_->prepareForExternalIO();
+    surface_->flush();
 }
 
 SkSurfaceProps ClientResourceProvider::ScopedSkSurface::ComputeSurfaceProps(
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 2fd05ae4..380e817 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -5,6 +5,7 @@
 #include "components/viz/common/features.h"
 
 #include "base/command_line.h"
+#include "base/metrics/field_trial_params.h"
 #include "build/build_config.h"
 #include "components/viz/common/switches.h"
 
@@ -14,6 +15,10 @@
 
 namespace features {
 
+constexpr char kProvider[] = "provider";
+constexpr char kDrawQuad[] = "draw_quad";
+constexpr char kSurfaceLayer[] = "surface_layer";
+
 #if defined(USE_AURA) || defined(OS_MACOSX)
 const base::Feature kEnableSurfaceSynchronization{
     "SurfaceSynchronization", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -35,13 +40,17 @@
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
-// Enables running the Viz-assisted hit-test logic.
+// Enables running the Viz-assisted hit-test logic. We still need to keep the
+// VizHitTestDrawQuad and VizHitTestSurfaceLayer features for finch launch.
 const base::Feature kEnableVizHitTestDrawQuad{"VizHitTestDrawQuad",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kEnableVizHitTestSurfaceLayer{
     "VizHitTestSurfaceLayer", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEnableVizHitTest{"VizHitTest",
+                                      base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Use the SkiaRenderer.
 const base::Feature kUseSkiaRenderer{"UseSkiaRenderer",
                                      base::FEATURE_DISABLED_BY_DEFAULT};
@@ -75,22 +84,32 @@
              switches::kEnableVizHitTestDebug);
 }
 
-bool IsVizHitTestingDrawQuadEnabled() {
-  if (IsVizHitTestingSurfaceLayerEnabled())
-    return false;
-  return base::FeatureList::IsEnabled(kEnableVizHitTestDrawQuad) ||
+// VizHitTest is considered enabled when any of its variant is turned on, or
+// when VizDisplayCompositor is turned on.
+bool IsVizHitTestingEnabled() {
+  return base::FeatureList::IsEnabled(features::kEnableVizHitTest) ||
          base::FeatureList::IsEnabled(kVizDisplayCompositor);
 }
 
-bool IsVizHitTestingEnabled() {
-  return IsVizHitTestingDrawQuadEnabled() ||
-         IsVizHitTestingSurfaceLayerEnabled();
+// VizHitTestDrawQuad is enabled when this feature is explicitly enabled on
+// chrome://flags, or when VizHitTest is enabled but VizHitTestSurfaceLayer is
+// turned off.
+bool IsVizHitTestingDrawQuadEnabled() {
+  return GetFieldTrialParamValueByFeature(features::kEnableVizHitTest,
+                                          kProvider) == kDrawQuad ||
+         (IsVizHitTestingEnabled() && !IsVizHitTestingSurfaceLayerEnabled());
 }
 
+// VizHitTestSurfaceLayer is enabled when this feature is explicitly enabled on
+// chrome://flags, or when it is enabled by finch and chrome://flags does not
+// conflict.
 bool IsVizHitTestingSurfaceLayerEnabled() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-             switches::kUseVizHitTestSurfaceLayer) ||
-         base::FeatureList::IsEnabled(kEnableVizHitTestSurfaceLayer);
+  return GetFieldTrialParamValueByFeature(features::kEnableVizHitTest,
+                                          kProvider) == kSurfaceLayer ||
+         (IsVizHitTestingEnabled() &&
+          GetFieldTrialParamValueByFeature(features::kEnableVizHitTest,
+                                           kProvider) != kDrawQuad &&
+          base::FeatureList::IsEnabled(kEnableVizHitTestSurfaceLayer));
 }
 
 bool IsUsingSkiaRenderer() {
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 1ddae70c..fd452ba 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -12,6 +12,7 @@
 namespace features {
 
 VIZ_COMMON_EXPORT extern const base::Feature kEnableSurfaceSynchronization;
+VIZ_COMMON_EXPORT extern const base::Feature kEnableVizHitTest;
 VIZ_COMMON_EXPORT extern const base::Feature kEnableVizHitTestDrawQuad;
 VIZ_COMMON_EXPORT extern const base::Feature kEnableVizHitTestSurfaceLayer;
 VIZ_COMMON_EXPORT extern const base::Feature kUseSkiaRenderer;
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index fc66a74..81464ed 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -1723,9 +1723,6 @@
 
 TYPED_TEST_SUITE(VideoRendererPixelTest, GLCapableRendererTypes);
 
-// TODO(crbug.com/936822): Remove this once it passes with SkiaRenderer.
-using VideoGLRendererPixelTest = VideoRendererPixelTest<GLRenderer>;
-
 template <typename RendererType>
 class VideoRendererPixelHiLoTest : public VideoRendererPixelTest<RendererType>,
                                    public testing::WithParamInterface<bool> {
@@ -1807,9 +1804,7 @@
   ClippedYUVRect();
 }
 
-// TODO(crbug.com/936822): Enable this test for SkiaRenderer
-// TYPED_TEST(VideoRendererPixelTest, OffsetYUVRect) {
-TEST_F(VideoGLRendererPixelTest, OffsetYUVRect) {
+TYPED_TEST(VideoRendererPixelTest, OffsetYUVRect) {
   gfx::Rect rect(this->device_viewport_size_);
 
   int id = 1;
@@ -1923,23 +1918,19 @@
 
 // Test that a YUV video doesn't bleed outside of its tex coords when the
 // tex coord rect is only a partial subrectangle of the coded contents.
-// TODO(crbug.com/936822): Enable this test for SkiaRenderer
-// TYPED_TEST(VideoRendererPixelTest, YUVEdgeBleed) {
-TEST_F(VideoGLRendererPixelTest, YUVEdgeBleed) {
+TYPED_TEST(VideoRendererPixelTest, YUVEdgeBleed) {
   RenderPassList pass_list;
-  CreateEdgeBleedPass(media::PIXEL_FORMAT_I420, gfx::ColorSpace::CreateJpeg(),
-                      &pass_list);
+  this->CreateEdgeBleedPass(media::PIXEL_FORMAT_I420,
+                            gfx::ColorSpace::CreateJpeg(), &pass_list);
   EXPECT_TRUE(this->RunPixelTest(&pass_list,
                                  base::FilePath(FILE_PATH_LITERAL("green.png")),
                                  cc::FuzzyPixelOffByOneComparator(true)));
 }
 
-// TODO(crbug.com/936822): Enable this test for SkiaRenderer
-// TYPED_TEST(VideoRendererPixelTest, YUVAEdgeBleed) {
-TEST_F(VideoGLRendererPixelTest, YUVAEdgeBleed) {
+TYPED_TEST(VideoRendererPixelTest, YUVAEdgeBleed) {
   RenderPassList pass_list;
-  CreateEdgeBleedPass(media::PIXEL_FORMAT_I420A,
-                      gfx::ColorSpace::CreateREC601(), &pass_list);
+  this->CreateEdgeBleedPass(media::PIXEL_FORMAT_I420A,
+                            gfx::ColorSpace::CreateREC601(), &pass_list);
   // Set the output color space to match the input primaries and transfer.
   pass_list.back()->color_space =
       gfx::ColorSpace(gfx::ColorSpace::PrimaryID::SMPTE170M,
@@ -1974,9 +1965,7 @@
       cc::FuzzyPixelOffByOneComparator(true)));
 }
 
-// TODO(crbug.com/936822): Enable this test for SkiaRenderer
-// TYPED_TEST(VideoRendererPixelTest, SimpleYUVARect) {
-TEST_F(VideoGLRendererPixelTest, SimpleYUVARect) {
+TYPED_TEST(VideoRendererPixelTest, SimpleYUVARect) {
   gfx::Rect rect(this->device_viewport_size_);
 
   int id = 1;
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 0e1baa3..bb80993b 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -295,6 +295,44 @@
   return nearest_neighbor ? kNone_SkFilterQuality : kLow_SkFilterQuality;
 }
 
+// Returns kFast if sampling outside of vis_tex_coords due to AA or bilerp will
+// not go outside of the content area, or if the content area is the full image
+// (in which case hardware clamping handles it automatically). Different quad
+// types have different rules for the content area within the image.
+SkCanvas::SrcRectConstraint GetTextureConstraint(
+    const SkImage* image,
+    const gfx::RectF& vis_tex_coords,
+    const gfx::RectF& valid_texel_bounds) {
+  bool fills_left = valid_texel_bounds.x() <= 0.f;
+  bool fills_right = valid_texel_bounds.right() >= image->width();
+  bool fills_top = valid_texel_bounds.y() <= 0.f;
+  bool fills_bottom = valid_texel_bounds.y() >= image->height();
+  if (fills_left && fills_right && fills_top && fills_bottom) {
+    // The entire image is contained in the content area, so hardware clamping
+    // ensures only content texels are sampled
+    return SkCanvas::kFast_SrcRectConstraint;
+  }
+
+  gfx::RectF safe_texels = valid_texel_bounds;
+  safe_texels.Inset(0.5f, 0.5f);
+
+  // Check each axis independently; tile quads may only need clamping on one
+  // side (e.g. right or bottom) and this logic doesn't fully match a simple
+  // contains() check.
+  if ((!fills_left && vis_tex_coords.x() < safe_texels.x()) ||
+      (!fills_right && vis_tex_coords.right() > safe_texels.right())) {
+    return SkCanvas::kStrict_SrcRectConstraint;
+  }
+  if ((!fills_top && vis_tex_coords.y() < safe_texels.y()) ||
+      (!fills_bottom && vis_tex_coords.bottom() > safe_texels.bottom())) {
+    return SkCanvas::kStrict_SrcRectConstraint;
+  }
+
+  // The texture coordinates are far enough from the content area that even with
+  // bilerp and AA, it won't sample outside the content area
+  return SkCanvas::kFast_SrcRectConstraint;
+}
+
 // Return a color filter that multiplies the incoming color by the fixed alpha
 sk_sp<SkColorFilter> MakeOpacityFilter(float alpha, sk_sp<SkColorFilter> in) {
   SkColor alpha_as_color = SkColorSetA(SK_ColorWHITE, 255 * alpha);
@@ -941,6 +979,49 @@
        params.aa_flags, params.draw_region.has_value()});
 }
 
+SkCanvas::SrcRectConstraint SkiaRenderer::ResolveTextureConstraints(
+    const SkImage* image,
+    const gfx::RectF& valid_texel_bounds,
+    DrawQuadParams* params) {
+  if (params->aa_flags == SkCanvas::kNone_QuadAAFlags &&
+      params->filter_quality == kNone_SkFilterQuality) {
+    // Non-AA and no bilinear filtering so rendering won't filter outside the
+    // provided texture coordinates.
+    return SkCanvas::kFast_SrcRectConstraint;
+  }
+
+  // Resolve texture coordinates against the valid content area of the image
+  SkCanvas::SrcRectConstraint constraint =
+      GetTextureConstraint(image, params->vis_tex_coords, valid_texel_bounds);
+
+  // Skia clamps to the provided texture coordinates, not the content_area. If
+  // there is a mismatch, have to update the draw params to account for the new
+  // constraint
+  if (constraint == SkCanvas::kFast_SrcRectConstraint ||
+      valid_texel_bounds == params->vis_tex_coords) {
+    return constraint;
+  }
+
+  // To get |valid_texel_bounds| as the constraint, it must be sent as the tex
+  // coords. To draw the right shape, store |visible_rect| as the |draw_region|
+  // and change the visible rect so that the mapping from |visible_rect| to
+  // |valid_texel_bounds| causes |draw_region| to map to original
+  // |vis_tex_coords|
+  if (!params->draw_region.has_value()) {
+    params->draw_region.emplace(gfx::QuadF(params->visible_rect));
+  }
+
+  // Preserve the src-to-dst transformation for the padded texture coords
+  SkMatrix src_to_dst = SkMatrix::MakeRectToRect(
+      gfx::RectFToSkRect(params->vis_tex_coords),
+      gfx::RectFToSkRect(params->visible_rect), SkMatrix::kFill_ScaleToFit);
+  params->visible_rect = gfx::SkRectToRectF(
+      src_to_dst.mapRect(gfx::RectFToSkRect(valid_texel_bounds)));
+  params->vis_tex_coords = valid_texel_bounds;
+
+  return SkCanvas::kStrict_SrcRectConstraint;
+}
+
 bool SkiaRenderer::MustFlushBatchedQuads(const DrawQuad* new_quad,
                                          const DrawQuadParams& params) {
   if (batched_quads_.empty())
@@ -969,14 +1050,25 @@
 }
 
 void SkiaRenderer::AddQuadToBatch(const SkImage* image,
+                                  const gfx::RectF& valid_texel_bounds,
                                   DrawQuadParams* params) {
+  SkCanvas::SrcRectConstraint constraint =
+      ResolveTextureConstraints(image, valid_texel_bounds, params);
+  // Last check for flushing the batch, since constraint can't be known until
+  // the last minute.
+  if (!batched_quads_.empty() && batched_quad_state_.constraint != constraint) {
+    FlushBatchedQuads();
+  }
+
   // Configure batch state if it's the first
   if (batched_quads_.empty()) {
     batched_quad_state_.scissor_rect = params->scissor_rect;
     batched_quad_state_.rounded_corner_bounds = params->rounded_corner_bounds;
     batched_quad_state_.blend_mode = params->blend_mode;
     batched_quad_state_.filter_quality = params->filter_quality;
+    batched_quad_state_.constraint = constraint;
   }
+  DCHECK(batched_quad_state_.constraint == constraint);
 
   // Add entry, with optional clip quad and shared transform
   if (params->draw_region.has_value()) {
@@ -1008,7 +1100,8 @@
   paint.setBlendMode(batched_quad_state_.blend_mode);
   current_canvas_->experimental_DrawEdgeAAImageSet(
       &batched_quads_.front(), batched_quads_.size(),
-      &batched_draw_regions_.front(), &batched_cdt_matrices_.front(), &paint);
+      &batched_draw_regions_.front(), &batched_cdt_matrices_.front(), &paint,
+      batched_quad_state_.constraint);
 
   batched_quads_.clear();
   batched_draw_regions_.clear();
@@ -1033,6 +1126,7 @@
 }
 
 void SkiaRenderer::DrawSingleImage(const SkImage* image,
+                                   const gfx::RectF& valid_texel_bounds,
                                    const SkPaint& paint,
                                    DrawQuadParams* params) {
   DCHECK(batched_quads_.empty());
@@ -1041,12 +1135,16 @@
   SkAutoCanvasRestore acr(current_canvas_, true /* do_save */);
   PrepareCanvas(params->scissor_rect, params->rounded_corner_bounds,
                 &params->content_device_transform);
+
+  SkCanvas::SrcRectConstraint constraint =
+      ResolveTextureConstraints(image, valid_texel_bounds, params);
+
   // Use -1 for matrix index since the cdt is set on the canvas.
   SkCanvas::ImageSetEntry entry = MakeEntry(image, -1, *params);
   const SkPoint* draw_region =
       params->draw_region.has_value() ? params->draw_region->points : nullptr;
   current_canvas_->experimental_DrawEdgeAAImageSet(&entry, 1, draw_region,
-                                                   nullptr, &paint);
+                                                   nullptr, &paint, constraint);
 }
 
 void SkiaRenderer::DrawDebugBorderQuad(const DebugBorderDrawQuad* quad,
@@ -1157,7 +1255,15 @@
       image->width(), image->height());
   params->vis_tex_coords = cc::MathUtil::ScaleRectProportional(
       uv_rect, gfx::RectF(quad->rect), params->visible_rect);
-  AddQuadToBatch(image, params);
+
+  // Use provided resource size if not empty, otherwise use the full image size
+  // as the content area
+  gfx::RectF valid_texel_bounds =
+      quad->resource_size_in_pixels().IsEmpty()
+          ? gfx::RectF(image->width(), image->height())
+          : gfx::RectF(gfx::SizeF(quad->resource_size_in_pixels()));
+
+  AddQuadToBatch(image, valid_texel_bounds, params);
 }
 
 void SkiaRenderer::DrawTextureQuad(const TextureDrawQuad* quad,
@@ -1175,6 +1281,13 @@
   params->vis_tex_coords = cc::MathUtil::ScaleRectProportional(
       uv_rect, gfx::RectF(quad->rect), params->visible_rect);
 
+  // Use provided resource size if not empty, otherwise use the full image size
+  // as the content area
+  gfx::RectF valid_texel_bounds =
+      quad->resource_size_in_pixels().IsEmpty()
+          ? gfx::RectF(image->width(), image->height())
+          : gfx::RectF(gfx::SizeF(quad->resource_size_in_pixels()));
+
   // There are two scenarios where a texture quad cannot be put into a batch:
   // 1. It needs to be blended with a constant background color.
   // 2. The vertex opacities are not all 1s.
@@ -1187,7 +1300,7 @@
   if (!blend_background && !vertex_alpha) {
     // This is a simple texture draw and can go into the batching system
     DCHECK(!MustFlushBatchedQuads(quad, *params));
-    AddQuadToBatch(image, params);
+    AddQuadToBatch(image, valid_texel_bounds, params);
     return;
   }
   // This needs a color filter for background blending and/or a mask filter
@@ -1264,7 +1377,7 @@
   // Override the default paint opacity since it may not be params.opacity
   paint.setAlphaf(quad_alpha);
 
-  DrawSingleImage(image, paint, params);
+  DrawSingleImage(image, valid_texel_bounds, paint, params);
 }
 
 void SkiaRenderer::DrawTileDrawQuad(const TileDrawQuad* quad,
@@ -1283,7 +1396,21 @@
 
   params->vis_tex_coords = cc::MathUtil::ScaleRectProportional(
       quad->tex_coord_rect, gfx::RectF(quad->rect), params->visible_rect);
-  AddQuadToBatch(image, params);
+  // When a tile is at the right or bottom edge of the entire tiled area, its
+  // images won't be fully filled so use the unclipped texture coords. On
+  // interior tiles or left/top tiles, the image has been filled with
+  // overlapping content so the entire image is valid for sampling.
+  gfx::RectF valid_texel_bounds(gfx::SizeF(quad->texture_size));
+  if (quad->IsRightEdge()) {
+    // Restrict the width to match tex coords
+    valid_texel_bounds.set_width(quad->tex_coord_rect.width());
+  }
+  if (quad->IsBottomEdge()) {
+    // Restrict the height to match tex coords
+    valid_texel_bounds.set_height(quad->tex_coord_rect.height());
+  }
+
+  AddQuadToBatch(image, valid_texel_bounds, params);
 }
 
 void SkiaRenderer::DrawYUVVideoQuad(const YUVVideoDrawQuad* quad,
@@ -1321,7 +1448,10 @@
   if (color_filter)
     paint.setColorFilter(color_filter);
 
-  DrawSingleImage(image, paint, params);
+  // Use provided, unclipped texture coordinates as the content area, which will
+  // force coord clamping unless the geometry was clipped, or they span the
+  // entire YUV image.
+  DrawSingleImage(image, quad->ya_tex_coord_rect, paint, params);
 }
 
 void SkiaRenderer::DrawUnsupportedQuad(const DrawQuad* quad,
@@ -1567,13 +1697,16 @@
 
   params->vis_tex_coords = cc::MathUtil::ScaleRectProportional(
       quad->tex_coord_rect, gfx::RectF(quad->rect), params->visible_rect);
+  gfx::RectF valid_texel_bounds(content_image->width(),
+                                content_image->height());
+
   if (params->filter_quality < kMedium_SkFilterQuality &&
       !rpdq_params.image_filter && !rpdq_params.backdrop_filter &&
       !rpdq_params.mask_image) {
     // We've checked enough to know that this is a plain textured draw that
     // is compatible with any batched images, so preserve that
     DCHECK(!MustFlushBatchedQuads(quad, *params));
-    AddQuadToBatch(content_image, params);
+    AddQuadToBatch(content_image, valid_texel_bounds, params);
     return;
   }
 
@@ -1592,7 +1725,7 @@
               &rpdq_params.mask_to_quad_matrix)));
       DCHECK(paint.getMaskFilter());
     }
-    DrawSingleImage(content_image, paint, params);
+    DrawSingleImage(content_image, valid_texel_bounds, paint, params);
     return;
   }
 
@@ -1656,11 +1789,13 @@
   SkPaint content_paint;
   content_paint.setFilterQuality(paint.getFilterQuality());
 
+  SkCanvas::SrcRectConstraint constraint =
+      ResolveTextureConstraints(content_image, valid_texel_bounds, params);
   SkCanvas::ImageSetEntry entry = MakeEntry(content_image, -1, *params);
   const SkPoint* draw_region =
       params->draw_region.has_value() ? params->draw_region->points : nullptr;
-  current_canvas_->experimental_DrawEdgeAAImageSet(&entry, 1, draw_region,
-                                                   nullptr, &content_paint);
+  current_canvas_->experimental_DrawEdgeAAImageSet(
+      &entry, 1, draw_region, nullptr, &content_paint, constraint);
 
   // And the saved layer will be auto-restored when |acr| is destructed
 }
diff --git a/components/viz/service/display/skia_renderer.h b/components/viz/service/display/skia_renderer.h
index 95106659..1c6afdc6 100644
--- a/components/viz/service/display/skia_renderer.h
+++ b/components/viz/service/display/skia_renderer.h
@@ -116,16 +116,25 @@
   SkCanvas::ImageSetEntry MakeEntry(const SkImage* image,
                                     int matrix_index,
                                     const DrawQuadParams& params);
+  // Returns overall constraint to pass to Skia, and modifies |params| to
+  // emulate content area clamping different from the provided texture coords.
+  SkCanvas::SrcRectConstraint ResolveTextureConstraints(
+      const SkImage* image,
+      const gfx::RectF& valid_texel_bounds,
+      DrawQuadParams* params);
 
   bool MustFlushBatchedQuads(const DrawQuad* new_quad,
                              const DrawQuadParams& params);
-  void AddQuadToBatch(const SkImage* image, DrawQuadParams* params);
+  void AddQuadToBatch(const SkImage* image,
+                      const gfx::RectF& valid_texel_bounds,
+                      DrawQuadParams* params);
   void FlushBatchedQuads();
 
   // Utility to draw a single quad as a filled color
   void DrawColoredQuad(SkColor color, DrawQuadParams* params);
   // Utility to make a single ImageSetEntry and draw it with the complex paint.
   void DrawSingleImage(const SkImage* image,
+                       const gfx::RectF& valid_texel_bounds,
                        const SkPaint& paint,
                        DrawQuadParams* params);
 
@@ -218,6 +227,7 @@
     base::Optional<gfx::RRectF> rounded_corner_bounds;
     SkBlendMode blend_mode;
     SkFilterQuality filter_quality;
+    SkCanvas::SrcRectConstraint constraint;
 
     BatchedQuadState();
   };
diff --git a/components/viz/service/surfaces/surface_allocation_group.cc b/components/viz/service/surfaces/surface_allocation_group.cc
index 6e60ce22..a61c55b 100644
--- a/components/viz/service/surfaces/surface_allocation_group.cc
+++ b/components/viz/service/surfaces/surface_allocation_group.cc
@@ -150,8 +150,8 @@
   surface->TakeActiveLatencyInfo(out);
   auto it = FindLatestSurfaceUpTo(surface->surface_id());
   DCHECK_EQ(*it, surface);
-  for (--it; it >= surfaces_.begin() && !(*it)->is_latency_info_taken(); --it)
-    (*it)->TakeActiveAndPendingLatencyInfo(out);
+  while (it > surfaces_.begin() && !(*it)->is_latency_info_taken())
+    (*--it)->TakeActiveAndPendingLatencyInfo(out);
 }
 
 void SurfaceAllocationGroup::OnFirstSurfaceActivation(Surface* surface) {
diff --git a/components/webcrypto/algorithm_dispatch.cc b/components/webcrypto/algorithm_dispatch.cc
index 20e7df8..87058546 100644
--- a/components/webcrypto/algorithm_dispatch.cc
+++ b/components/webcrypto/algorithm_dispatch.cc
@@ -317,12 +317,6 @@
                    import_algorithm, extractable, usages, derived_key);
 }
 
-std::unique_ptr<blink::WebCryptoDigestor> CreateDigestor(
-    blink::WebCryptoAlgorithmId algorithm) {
-  crypto::EnsureOpenSSLInit();
-  return CreateDigestorImplementation(algorithm);
-}
-
 bool SerializeKeyForClone(const blink::WebCryptoKey& key,
                           blink::WebVector<uint8_t>* key_data) {
   const AlgorithmImplementation* impl = nullptr;
diff --git a/components/webcrypto/algorithm_dispatch.h b/components/webcrypto/algorithm_dispatch.h
index 713e03f..fe3d9e5d 100644
--- a/components/webcrypto/algorithm_dispatch.h
+++ b/components/webcrypto/algorithm_dispatch.h
@@ -114,9 +114,6 @@
                  blink::WebCryptoKeyUsageMask usages,
                  blink::WebCryptoKey* derived_key);
 
-std::unique_ptr<blink::WebCryptoDigestor> CreateDigestor(
-    blink::WebCryptoAlgorithmId algorithm);
-
 bool SerializeKeyForClone(const blink::WebCryptoKey& key,
                           blink::WebVector<uint8_t>* key_data);
 
diff --git a/components/webcrypto/algorithm_implementations.h b/components/webcrypto/algorithm_implementations.h
index 94f0bb9..186256c 100644
--- a/components/webcrypto/algorithm_implementations.h
+++ b/components/webcrypto/algorithm_implementations.h
@@ -14,9 +14,6 @@
 
 class AlgorithmImplementation;
 
-std::unique_ptr<blink::WebCryptoDigestor> CreateDigestorImplementation(
-    blink::WebCryptoAlgorithmId algorithm);
-
 std::unique_ptr<AlgorithmImplementation> CreateShaImplementation();
 std::unique_ptr<AlgorithmImplementation> CreateAesCbcImplementation();
 std::unique_ptr<AlgorithmImplementation> CreateAesCtrImplementation();
diff --git a/components/webcrypto/algorithms/sha.cc b/components/webcrypto/algorithms/sha.cc
index 60c64f9..1b44bf5b60 100644
--- a/components/webcrypto/algorithms/sha.cc
+++ b/components/webcrypto/algorithms/sha.cc
@@ -18,100 +18,40 @@
 
 namespace {
 
-// Implementation of blink::WebCryptoDigester, an internal Blink detail not
-// part of WebCrypto, that allows chunks of data to be streamed in before
-// computing a SHA-* digest (as opposed to ShaImplementation, which computes
-// digests over complete messages)
-class DigestorImpl : public blink::WebCryptoDigestor {
- public:
-  explicit DigestorImpl(blink::WebCryptoAlgorithmId algorithm_id)
-      : initialized_(false),
-        algorithm_id_(algorithm_id) {}
-
-  bool Consume(const unsigned char* data, unsigned int size) override {
-    return ConsumeWithStatus(data, size).IsSuccess();
-  }
-
-  Status ConsumeWithStatus(const unsigned char* data, unsigned int size) {
-    crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
-    Status error = Init();
-    if (!error.IsSuccess())
-      return error;
-
-    if (!EVP_DigestUpdate(digest_context_.get(), data, size))
-      return Status::OperationError();
-
-    return Status::Success();
-  }
-
-  bool Finish(unsigned char*& result_data,
-              unsigned int& result_data_size) override {
-    Status error = FinishInternal(result_, &result_data_size);
-    if (!error.IsSuccess())
-      return false;
-    result_data = result_;
-    return true;
-  }
-
-  Status FinishWithVectorAndStatus(std::vector<uint8_t>* result) {
-    const size_t hash_expected_size = EVP_MD_CTX_size(digest_context_.get());
-    result->resize(hash_expected_size);
-    unsigned int hash_buffer_size;  // ignored
-    return FinishInternal(result->data(), &hash_buffer_size);
-  }
-
- private:
-  Status Init() {
-    if (initialized_)
-      return Status::Success();
-
-    const EVP_MD* digest_algorithm = GetDigest(algorithm_id_);
-    if (!digest_algorithm)
-      return Status::ErrorUnsupported();
-
-    if (!EVP_DigestInit_ex(digest_context_.get(), digest_algorithm, nullptr))
-      return Status::OperationError();
-
-    initialized_ = true;
-    return Status::Success();
-  }
-
-  Status FinishInternal(unsigned char* result, unsigned int* result_size) {
-    crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
-    Status error = Init();
-    if (!error.IsSuccess())
-      return error;
-
-    const size_t hash_expected_size = EVP_MD_CTX_size(digest_context_.get());
-    if (hash_expected_size == 0)
-      return Status::ErrorUnexpected();
-    DCHECK_LE(hash_expected_size, static_cast<unsigned>(EVP_MAX_MD_SIZE));
-
-    if (!EVP_DigestFinal_ex(digest_context_.get(), result, result_size) ||
-        *result_size != hash_expected_size)
-      return Status::OperationError();
-
-    return Status::Success();
-  }
-
-  bool initialized_;
-  bssl::ScopedEVP_MD_CTX digest_context_;
-  blink::WebCryptoAlgorithmId algorithm_id_;
-  unsigned char result_[EVP_MAX_MD_SIZE];
-};
-
 class ShaImplementation : public AlgorithmImplementation {
  public:
   Status Digest(const blink::WebCryptoAlgorithm& algorithm,
                 const CryptoData& data,
                 std::vector<uint8_t>* buffer) const override {
-    DigestorImpl digestor(algorithm.Id());
-    Status error = digestor.ConsumeWithStatus(data.bytes(), data.byte_length());
     // http://crbug.com/366427: the spec does not define any other failures for
     // digest, so none of the subsequent errors are spec compliant.
-    if (!error.IsSuccess())
-      return error;
-    return digestor.FinishWithVectorAndStatus(buffer);
+    bssl::ScopedEVP_MD_CTX digest_context;
+    crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+    const EVP_MD* digest_algorithm = GetDigest(algorithm.Id());
+    if (!digest_algorithm)
+      return Status::ErrorUnsupported();
+
+    if (!EVP_DigestInit_ex(digest_context.get(), digest_algorithm, nullptr))
+      return Status::OperationError();
+
+    if (!EVP_DigestUpdate(digest_context.get(), data.bytes(),
+                          data.byte_length()))
+      return Status::OperationError();
+
+    const size_t hash_expected_size = EVP_MD_CTX_size(digest_context.get());
+    if (hash_expected_size == 0)
+      return Status::ErrorUnexpected();
+    DCHECK_LE(hash_expected_size, static_cast<unsigned>(EVP_MAX_MD_SIZE));
+
+    buffer->resize(hash_expected_size);
+    unsigned result_size;  // ignored
+    if (!EVP_DigestFinal_ex(digest_context.get(), buffer->data(),
+                            &result_size) ||
+        result_size != hash_expected_size)
+      return Status::OperationError();
+
+    return Status::Success();
   }
 };
 
@@ -121,9 +61,4 @@
   return std::make_unique<ShaImplementation>();
 }
 
-std::unique_ptr<blink::WebCryptoDigestor> CreateDigestorImplementation(
-    blink::WebCryptoAlgorithmId algorithm) {
-  return std::unique_ptr<blink::WebCryptoDigestor>(new DigestorImpl(algorithm));
-}
-
 }  // namespace webcrypto
diff --git a/components/webcrypto/algorithms/sha_unittest.cc b/components/webcrypto/algorithms/sha_unittest.cc
index 8363ca36..80ec9ae 100644
--- a/components/webcrypto/algorithms/sha_unittest.cc
+++ b/components/webcrypto/algorithms/sha_unittest.cc
@@ -43,44 +43,6 @@
   }
 }
 
-TEST_F(WebCryptoShaTest, DigestSampleSetsInChunks) {
-  base::ListValue tests;
-  ASSERT_TRUE(ReadJsonTestFileToList("sha.json", &tests));
-
-  for (size_t test_index = 0; test_index < tests.GetSize(); ++test_index) {
-    SCOPED_TRACE(test_index);
-    base::DictionaryValue* test;
-    ASSERT_TRUE(tests.GetDictionary(test_index, &test));
-
-    blink::WebCryptoAlgorithm test_algorithm =
-        GetDigestAlgorithm(test, "algorithm");
-    std::vector<uint8_t> test_input = GetBytesFromHexString(test, "input");
-    std::vector<uint8_t> test_output = GetBytesFromHexString(test, "output");
-
-    // Test the chunk version of the digest functions. Test with 129 byte chunks
-    // because the SHA-512 chunk size is 128 bytes.
-    unsigned char* output;
-    unsigned int output_length;
-    static const size_t kChunkSizeBytes = 129;
-    size_t length = test_input.size();
-    std::unique_ptr<blink::WebCryptoDigestor> digestor(
-        CreateDigestor(test_algorithm.Id()));
-    auto begin = test_input.begin();
-    size_t chunk_index = 0;
-    while (begin != test_input.end()) {
-      size_t chunk_length = std::min(kChunkSizeBytes, length - chunk_index);
-      std::vector<uint8_t> chunk(begin, begin + chunk_length);
-      ASSERT_TRUE(chunk.size() > 0);
-      EXPECT_TRUE(digestor->Consume(chunk.data(),
-                                    static_cast<unsigned int>(chunk.size())));
-      chunk_index = chunk_index + chunk_length;
-      begin = begin + chunk_length;
-    }
-    EXPECT_TRUE(digestor->Finish(output, output_length));
-    EXPECT_BYTES_EQ(test_output, CryptoData(output, output_length));
-  }
-}
-
 }  // namespace
 
 }  // namespace webcrypto
diff --git a/components/webcrypto/webcrypto_impl.cc b/components/webcrypto/webcrypto_impl.cc
index ffd52546..67ad546 100644
--- a/components/webcrypto/webcrypto_impl.cc
+++ b/components/webcrypto/webcrypto_impl.cc
@@ -822,11 +822,6 @@
   }
 }
 
-std::unique_ptr<blink::WebCryptoDigestor> WebCryptoImpl::CreateDigestor(
-    blink::WebCryptoAlgorithmId algorithm_id) {
-  return webcrypto::CreateDigestor(algorithm_id);
-}
-
 bool WebCryptoImpl::DeserializeKeyForClone(
     const blink::WebCryptoKeyAlgorithm& algorithm,
     blink::WebCryptoKeyType type,
diff --git a/components/webcrypto/webcrypto_impl.h b/components/webcrypto/webcrypto_impl.h
index 36156aa..1dd6021 100644
--- a/components/webcrypto/webcrypto_impl.h
+++ b/components/webcrypto/webcrypto_impl.h
@@ -108,13 +108,6 @@
       blink::WebCryptoResult result,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner) override;
 
-  // This method returns a digestor object that can be used to synchronously
-  // compute a digest one chunk at a time. Thus, the consume does not need to
-  // hold onto a large buffer with all the data to digest. Chunks can be given
-  // one at a time and the digest will be computed piecemeal.
-  std::unique_ptr<blink::WebCryptoDigestor> CreateDigestor(
-      blink::WebCryptoAlgorithmId algorithm_id) override;
-
   bool DeserializeKeyForClone(const blink::WebCryptoKeyAlgorithm& algorithm,
                               blink::WebCryptoKeyType type,
                               bool extractable,
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index ecbc5a0..272fab4 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -85,6 +85,10 @@
       return ui::ToString(static_cast<ax::mojom::Restriction>(value));
     case ax::mojom::IntAttribute::kSortDirection:
       return ui::ToString(static_cast<ax::mojom::SortDirection>(value));
+    case ax::mojom::IntAttribute::kTextOverlineStyle:
+    case ax::mojom::IntAttribute::kTextStrikethroughStyle:
+    case ax::mojom::IntAttribute::kTextUnderlineStyle:
+      return ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(value));
     case ax::mojom::IntAttribute::kTextDirection:
       return ui::ToString(static_cast<ax::mojom::TextDirection>(value));
     case ax::mojom::IntAttribute::kTextPosition:
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index abb70db8..b3b913e6 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -1871,6 +1871,11 @@
   RunHtmlTest(FILE_PATH_LITERAL("table-multiple-row-and-column-headers.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityTextDecorationStyles) {
+  RunHtmlTest(FILE_PATH_LITERAL("text-decoration-styles.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityTextarea) {
   RunHtmlTest(FILE_PATH_LITERAL("textarea.html"));
 }
diff --git a/content/browser/blob_storage/blob_transport_host_unittest.cc b/content/browser/blob_storage/blob_transport_host_unittest.cc
deleted file mode 100644
index 9dcaaf9..0000000
--- a/content/browser/blob_storage/blob_transport_host_unittest.cc
+++ /dev/null
@@ -1,523 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <stddef.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/memory/shared_memory.h"
-#include "base/run_loop.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "storage/browser/blob/blob_data_builder.h"
-#include "storage/browser/blob/blob_data_handle.h"
-#include "storage/browser/blob/blob_storage_context.h"
-#include "storage/browser/blob/blob_transport_host.h"
-#include "storage/common/blob_storage/blob_storage_constants.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-namespace {
-const std::string kBlobUUID = "blobUUIDYAY";
-const std::string kContentType = "content_type";
-const std::string kContentDisposition = "content_disposition";
-const std::string kCompletedBlobUUID = "completedBlob";
-const std::string kCompletedBlobData = "completedBlobData";
-
-const size_t kTestBlobStorageIPCThresholdBytes = 5;
-const size_t kTestBlobStorageMaxSharedMemoryBytes = 20;
-
-const size_t kTestBlobStorageMaxBlobMemorySize = 400;
-const uint64_t kTestBlobStorageMaxDiskSpace = 4000;
-const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
-const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
-
-void PopulateBytes(char* bytes, size_t length) {
-  for (size_t i = 0; i < length; i++) {
-    bytes[i] = static_cast<char>(i);
-  }
-}
-
-void AddMemoryItem(size_t length, std::vector<network::DataElement>* out) {
-  network::DataElement bytes;
-  bytes.SetToBytesDescription(length);
-  out->push_back(std::move(bytes));
-}
-
-void AddShortcutMemoryItem(size_t length,
-                           std::vector<network::DataElement>* out) {
-  network::DataElement bytes;
-  bytes.SetToAllocatedBytes(length);
-  PopulateBytes(bytes.mutable_bytes(), length);
-  out->push_back(std::move(bytes));
-}
-
-void AddShortcutMemoryItem(size_t length, storage::BlobDataBuilder* out) {
-  network::DataElement bytes;
-  bytes.SetToAllocatedBytes(length);
-  PopulateBytes(bytes.mutable_bytes(), length);
-  out->AppendData(bytes.bytes(), length);
-}
-
-void AddBlobItem(std::vector<network::DataElement>* out) {
-  network::DataElement blob;
-  blob.SetToBlob(kCompletedBlobUUID);
-  out->push_back(std::move(blob));
-}
-}  // namespace
-
-class BlobTransportHostTest : public testing::Test {
- public:
-  BlobTransportHostTest()
-      : status_code_(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS),
-        request_called_(false) {}
-  ~BlobTransportHostTest() override {}
-
-  void SetUp() override {
-    status_code_ = storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
-    request_called_ = false;
-    requests_.clear();
-    memory_handles_.clear();
-    storage::BlobStorageLimits limits;
-    limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
-    limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
-    limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize;
-    limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace;
-    limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace;
-    limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes;
-    limits.max_file_size = kTestBlobStorageMaxFileSizeBytes;
-    context_.mutable_memory_controller()->set_limits_for_testing(limits);
-    storage::BlobDataBuilder builder(kCompletedBlobUUID);
-    builder.AppendData(kCompletedBlobData);
-    completed_blob_handle_ = context_.AddFinishedBlob(builder);
-    EXPECT_EQ(storage::BlobStatus::DONE,
-              completed_blob_handle_->GetBlobStatus());
-  }
-
-  void StatusCallback(storage::BlobStatus status) {
-    status_called_ = true;
-    status_code_ = status;
-  }
-
-  void RequestMemoryCallback(
-      std::vector<storage::BlobItemBytesRequest> requests,
-      std::vector<base::SharedMemoryHandle> shared_memory_handles,
-      std::vector<base::File> files) {
-    requests_ = std::move(requests);
-    memory_handles_ = std::move(shared_memory_handles);
-    request_called_ = true;
-  }
-
-  storage::BlobStatus BuildBlobAsync(
-      const std::string& uuid,
-      const std::vector<network::DataElement>& descriptions,
-      std::unique_ptr<storage::BlobDataHandle>* storage) {
-    EXPECT_NE(storage, nullptr);
-    request_called_ = false;
-    status_called_ = false;
-    *storage = host_.StartBuildingBlob(
-        uuid, kContentType, kContentDisposition, descriptions, &context_,
-        nullptr,
-        base::Bind(&BlobTransportHostTest::RequestMemoryCallback,
-                   base::Unretained(this)),
-        base::Bind(&BlobTransportHostTest::StatusCallback,
-                   base::Unretained(this)));
-    if (status_called_)
-      return status_code_;
-    else
-      return context_.GetBlobStatus(uuid);
-  }
-
-  storage::BlobStatus GetBlobStatus(const std::string& uuid) const {
-    return context_.GetBlobStatus(uuid);
-  }
-
-  bool IsBeingBuiltInContext(const std::string& uuid) const {
-    return BlobStatusIsPending(context_.GetBlobStatus(uuid));
-  }
-
-  TestBrowserThreadBundle browser_thread_bundle_;
-  storage::BlobStorageContext context_;
-  storage::BlobTransportHost host_;
-  bool status_called_;
-  storage::BlobStatus status_code_;
-
-  bool request_called_;
-  std::vector<storage::BlobItemBytesRequest> requests_;
-  std::vector<base::SharedMemoryHandle> memory_handles_;
-  std::unique_ptr<storage::BlobDataHandle> completed_blob_handle_;
-};
-
-// The 'shortcut' method is when the data is included in the initial IPCs and
-// the browser uses that instead of requesting the memory.
-TEST_F(BlobTransportHostTest, TestShortcut) {
-  std::vector<network::DataElement> descriptions;
-
-  AddShortcutMemoryItem(10, &descriptions);
-  AddBlobItem(&descriptions);
-  AddShortcutMemoryItem(300, &descriptions);
-
-  storage::BlobDataBuilder expected(kBlobUUID);
-  expected.set_content_type(kContentType);
-  expected.set_content_disposition(kContentDisposition);
-  AddShortcutMemoryItem(10, &expected);
-  expected.AppendData(kCompletedBlobData);
-  AddShortcutMemoryItem(300, &expected);
-
-  std::unique_ptr<storage::BlobDataHandle> handle;
-  EXPECT_EQ(storage::BlobStatus::DONE,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle));
-
-  EXPECT_FALSE(request_called_);
-  EXPECT_EQ(0u, host_.blob_building_count());
-  EXPECT_FALSE(handle->IsBeingBuilt());
-  ASSERT_FALSE(handle->IsBroken());
-  std::unique_ptr<storage::BlobDataSnapshot> data = handle->CreateSnapshot();
-  EXPECT_EQ(expected, *data);
-  data.reset();
-  handle.reset();
-  base::RunLoop().RunUntilIdle();
-};
-
-TEST_F(BlobTransportHostTest, TestShortcutNoRoom) {
-  std::vector<network::DataElement> descriptions;
-
-  AddShortcutMemoryItem(10, &descriptions);
-  AddBlobItem(&descriptions);
-  AddShortcutMemoryItem(5000, &descriptions);
-
-  std::unique_ptr<storage::BlobDataHandle> handle;
-  EXPECT_EQ(storage::BlobStatus::ERR_OUT_OF_MEMORY,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle));
-
-  EXPECT_FALSE(request_called_);
-  EXPECT_EQ(0u, host_.blob_building_count());
-};
-
-TEST_F(BlobTransportHostTest, TestSingleSharedMemRequest) {
-  std::vector<network::DataElement> descriptions;
-  const size_t kSize = kTestBlobStorageIPCThresholdBytes + 1;
-  AddMemoryItem(kSize, &descriptions);
-
-  std::unique_ptr<storage::BlobDataHandle> handle;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle));
-  EXPECT_TRUE(handle);
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus());
-
-  EXPECT_TRUE(request_called_);
-  EXPECT_EQ(1u, host_.blob_building_count());
-  ASSERT_EQ(1u, requests_.size());
-  request_called_ = false;
-
-  EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest(
-                0, 0, 0, kSize, 0, 0),
-            requests_.at(0));
-};
-
-TEST_F(BlobTransportHostTest, TestMultipleSharedMemRequests) {
-  std::vector<network::DataElement> descriptions;
-  const size_t kSize = kTestBlobStorageMaxSharedMemoryBytes + 1;
-  const char kFirstBlockByte = 7;
-  const char kSecondBlockByte = 19;
-  AddMemoryItem(kSize, &descriptions);
-
-  storage::BlobDataBuilder expected(kBlobUUID);
-  expected.set_content_type(kContentType);
-  expected.set_content_disposition(kContentDisposition);
-  char data[kSize];
-  memset(data, kFirstBlockByte, kTestBlobStorageMaxSharedMemoryBytes);
-  expected.AppendData(data, kTestBlobStorageMaxSharedMemoryBytes);
-  expected.AppendData(&kSecondBlockByte, 1);
-
-  std::unique_ptr<storage::BlobDataHandle> handle;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle));
-
-  EXPECT_TRUE(request_called_);
-  EXPECT_EQ(1u, host_.blob_building_count());
-  ASSERT_EQ(1u, requests_.size());
-  request_called_ = false;
-
-  // We need to grab a duplicate handle so we can have two blocks open at the
-  // same time.
-  base::SharedMemoryHandle shared_mem_handle =
-      base::SharedMemory::DuplicateHandle(memory_handles_.at(0));
-  EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_mem_handle));
-  base::SharedMemory shared_memory(shared_mem_handle, false);
-  EXPECT_TRUE(shared_memory.Map(kTestBlobStorageMaxSharedMemoryBytes));
-
-  EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest(
-                0, 0, 0, kTestBlobStorageMaxSharedMemoryBytes, 0, 0),
-            requests_.at(0));
-
-  memset(shared_memory.memory(), kFirstBlockByte,
-         kTestBlobStorageMaxSharedMemoryBytes);
-
-  storage::BlobItemBytesResponse response(0);
-  std::vector<storage::BlobItemBytesResponse> responses = {response};
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, GetBlobStatus(kBlobUUID));
-  ASSERT_TRUE(handle);
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus());
-
-  EXPECT_TRUE(request_called_);
-  EXPECT_EQ(1u, host_.blob_building_count());
-  ASSERT_EQ(1u, requests_.size());
-  request_called_ = false;
-
-  EXPECT_EQ(storage::BlobItemBytesRequest::CreateSharedMemoryRequest(
-                1, 0, kTestBlobStorageMaxSharedMemoryBytes, 1, 0, 0),
-            requests_.at(0));
-
-  memset(shared_memory.memory(), kSecondBlockByte, 1);
-
-  response.request_number = 1;
-  responses[0] = response;
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_TRUE(handle);
-  EXPECT_EQ(storage::BlobStatus::DONE, handle->GetBlobStatus());
-  EXPECT_FALSE(request_called_);
-  EXPECT_EQ(0u, host_.blob_building_count());
-  std::unique_ptr<storage::BlobDataHandle> blob_handle =
-      context_.GetBlobDataFromUUID(kBlobUUID);
-  EXPECT_FALSE(blob_handle->IsBeingBuilt());
-  EXPECT_FALSE(blob_handle->IsBroken());
-  std::unique_ptr<storage::BlobDataSnapshot> blob_data =
-      blob_handle->CreateSnapshot();
-  EXPECT_EQ(expected, *blob_data);
-};
-
-TEST_F(BlobTransportHostTest, TestBasicIPCAndStopBuilding) {
-  std::vector<network::DataElement> descriptions;
-
-  AddMemoryItem(2, &descriptions);
-  AddBlobItem(&descriptions);
-  AddMemoryItem(2, &descriptions);
-
-  storage::BlobDataBuilder expected(kBlobUUID);
-  expected.set_content_type(kContentType);
-  expected.set_content_disposition(kContentDisposition);
-  AddShortcutMemoryItem(2, &expected);
-  expected.AppendData(kCompletedBlobData);
-  AddShortcutMemoryItem(2, &expected);
-
-  std::unique_ptr<storage::BlobDataHandle> handle1;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle1));
-  EXPECT_TRUE(handle1);
-  host_.CancelBuildingBlob(kBlobUUID, storage::BlobStatus::ERR_OUT_OF_MEMORY,
-                           &context_);
-
-  // Check that we're broken, and then remove the blob.
-  EXPECT_FALSE(handle1->IsBeingBuilt());
-  EXPECT_TRUE(handle1->IsBroken());
-  handle1.reset();
-  base::RunLoop().RunUntilIdle();
-  handle1 = context_.GetBlobDataFromUUID(kBlobUUID);
-  EXPECT_FALSE(handle1.get());
-
-  // This should succeed because we've removed all references to the blob.
-  std::unique_ptr<storage::BlobDataHandle> handle2;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle2));
-
-  EXPECT_TRUE(request_called_);
-  EXPECT_EQ(1u, host_.blob_building_count());
-  request_called_ = false;
-
-  storage::BlobItemBytesResponse response1(0);
-  PopulateBytes(response1.allocate_mutable_data(2), 2);
-  storage::BlobItemBytesResponse response2(1);
-  PopulateBytes(response2.allocate_mutable_data(2), 2);
-  std::vector<storage::BlobItemBytesResponse> responses = {response1,
-                                                           response2};
-
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::DONE, handle2->GetBlobStatus());
-  EXPECT_FALSE(request_called_);
-  EXPECT_EQ(0u, host_.blob_building_count());
-  EXPECT_FALSE(handle2->IsBeingBuilt());
-  EXPECT_FALSE(handle2->IsBroken());
-  std::unique_ptr<storage::BlobDataSnapshot> blob_data =
-      handle2->CreateSnapshot();
-  EXPECT_EQ(expected, *blob_data);
-};
-
-TEST_F(BlobTransportHostTest, TestBreakingAllBuilding) {
-  const std::string& kBlob1 = "blob1";
-  const std::string& kBlob2 = "blob2";
-  const std::string& kBlob3 = "blob3";
-
-  std::vector<network::DataElement> descriptions;
-  AddMemoryItem(2, &descriptions);
-
-  // Register blobs.
-  std::unique_ptr<storage::BlobDataHandle> handle1;
-  std::unique_ptr<storage::BlobDataHandle> handle2;
-  std::unique_ptr<storage::BlobDataHandle> handle3;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob1, descriptions, &handle1));
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob2, descriptions, &handle2));
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob3, descriptions, &handle3));
-
-  EXPECT_TRUE(request_called_);
-  EXPECT_TRUE(handle1->IsBeingBuilt() && handle2->IsBeingBuilt() &&
-              handle3->IsBeingBuilt());
-  EXPECT_FALSE(handle1->IsBroken() || handle2->IsBroken() ||
-               handle3->IsBroken());
-
-  EXPECT_TRUE(IsBeingBuiltInContext(kBlob1) && IsBeingBuiltInContext(kBlob2) &&
-              IsBeingBuiltInContext(kBlob3));
-
-  // This shouldn't call the transport complete callbacks, so our handles should
-  // still be false.
-  host_.CancelAll(&context_);
-
-  EXPECT_FALSE(handle1->IsBeingBuilt() || handle2->IsBeingBuilt() ||
-               handle3->IsBeingBuilt());
-  EXPECT_TRUE(handle1->IsBroken() && handle2->IsBroken() &&
-              handle3->IsBroken());
-
-  base::RunLoop().RunUntilIdle();
-};
-
-TEST_F(BlobTransportHostTest, TestBadIPCs) {
-  std::vector<network::DataElement> descriptions;
-
-  // Test reusing same blob uuid.
-  AddMemoryItem(10, &descriptions);
-  AddBlobItem(&descriptions);
-  AddMemoryItem(300, &descriptions);
-  std::unique_ptr<storage::BlobDataHandle> handle1;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle1));
-  EXPECT_TRUE(host_.IsBeingBuilt(kBlobUUID));
-  EXPECT_TRUE(request_called_);
-  host_.CancelBuildingBlob(
-      kBlobUUID, storage::BlobStatus::ERR_REFERENCED_BLOB_BROKEN, &context_);
-  handle1.reset();
-  EXPECT_FALSE(context_.GetBlobDataFromUUID(kBlobUUID).get());
-
-  // Test empty responses.
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle1));
-  std::vector<storage::BlobItemBytesResponse> responses;
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
-            handle1->GetBlobStatus());
-  handle1.reset();
-
-  // Test response problems below here.
-  descriptions.clear();
-  AddMemoryItem(2, &descriptions);
-  AddBlobItem(&descriptions);
-  AddMemoryItem(2, &descriptions);
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle1));
-
-  // Invalid request number.
-  storage::BlobItemBytesResponse response1(3);
-  PopulateBytes(response1.allocate_mutable_data(2), 2);
-  responses = {response1};
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
-            handle1->GetBlobStatus());
-  EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlobUUID)->IsBroken());
-  handle1.reset();
-  base::RunLoop().RunUntilIdle();
-
-  // Duplicate request number responses.
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlobUUID, descriptions, &handle1));
-  response1.request_number = 0;
-  storage::BlobItemBytesResponse response2(0);
-  PopulateBytes(response2.allocate_mutable_data(2), 2);
-  responses = {response1, response2};
-  host_.OnMemoryResponses(kBlobUUID, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
-            handle1->GetBlobStatus());
-  EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlobUUID)->IsBroken());
-  handle1.reset();
-  base::RunLoop().RunUntilIdle();
-};
-
-TEST_F(BlobTransportHostTest, WaitOnReferencedBlob) {
-  const std::string& kBlob1 = "blob1";
-  const std::string& kBlob2 = "blob2";
-  const std::string& kBlob3 = "blob3";
-
-  std::vector<network::DataElement> descriptions;
-  AddMemoryItem(2, &descriptions);
-
-  // Register blobs.
-  std::unique_ptr<storage::BlobDataHandle> handle1;
-
-  std::unique_ptr<storage::BlobDataHandle> handle2;
-  std::unique_ptr<storage::BlobDataHandle> handle3;
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob1, descriptions, &handle1));
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob2, descriptions, &handle2));
-  EXPECT_TRUE(request_called_);
-  request_called_ = false;
-
-  // Finish the third one, with a reference to the first and second blob.
-  network::DataElement element;
-  element.SetToBlob(kBlob1);
-  descriptions.push_back(std::move(element));
-  element.SetToBlob(kBlob2);
-  descriptions.push_back(std::move(element));
-
-  EXPECT_EQ(storage::BlobStatus::PENDING_TRANSPORT,
-            BuildBlobAsync(kBlob3, descriptions, &handle3));
-  EXPECT_TRUE(request_called_);
-  request_called_ = false;
-
-  // Finish the third, but we should still be 'building' it.
-  storage::BlobItemBytesResponse response1(0);
-  PopulateBytes(response1.allocate_mutable_data(2), 2);
-  std::vector<storage::BlobItemBytesResponse> responses = {response1};
-  host_.OnMemoryResponses(kBlob3, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::PENDING_REFERENCED_BLOBS,
-            handle3->GetBlobStatus());
-  EXPECT_FALSE(request_called_);
-  EXPECT_FALSE(host_.IsBeingBuilt(kBlob3));
-  EXPECT_TRUE(IsBeingBuiltInContext(kBlob3));
-
-  // Finish the first.
-  descriptions.clear();
-  AddShortcutMemoryItem(2, &descriptions);
-  host_.OnMemoryResponses(kBlob1, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::DONE, handle1->GetBlobStatus());
-  EXPECT_FALSE(request_called_);
-  EXPECT_FALSE(host_.IsBeingBuilt(kBlob1));
-  EXPECT_FALSE(IsBeingBuiltInContext(kBlob1));
-  EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob1));
-
-  // Run the message loop so we propogate the construction complete callbacks.
-  base::RunLoop().RunUntilIdle();
-  // Verify we're not done.
-  EXPECT_TRUE(IsBeingBuiltInContext(kBlob3));
-
-  // Finish the second.
-  host_.OnMemoryResponses(kBlob2, responses, &context_);
-  EXPECT_EQ(storage::BlobStatus::DONE, handle2->GetBlobStatus());
-  EXPECT_FALSE(request_called_);
-  EXPECT_FALSE(host_.IsBeingBuilt(kBlob2));
-  EXPECT_FALSE(IsBeingBuiltInContext(kBlob2));
-  EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob2));
-
-  // Run the message loop so we propogate the construction complete callbacks.
-  base::RunLoop().RunUntilIdle();
-  // Finally, we should be finished with third blob.
-  EXPECT_FALSE(host_.IsBeingBuilt(kBlob3));
-  EXPECT_FALSE(IsBeingBuiltInContext(kBlob3));
-  EXPECT_TRUE(context_.GetBlobDataFromUUID(kBlob3));
-};
-
-}  // namespace content
diff --git a/content/browser/devtools/devtools_session_encoding.cc b/content/browser/devtools/devtools_session_encoding.cc
index 6021b4d..aa5f39f 100644
--- a/content/browser/devtools/devtools_session_encoding.cc
+++ b/content/browser/devtools/devtools_session_encoding.cc
@@ -73,7 +73,7 @@
   std::unique_ptr<StreamingParserHandler> encoder =
       NewCBOREncoder(&cbor, &status);
   ParseJSON(
-      &platform,
+      platform,
       span<uint8_t>(reinterpret_cast<const uint8_t*>(json.data()), json.size()),
       encoder.get());
   if (!status.ok()) {
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 89c2fbd6..4be51c9 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -899,7 +899,9 @@
 
   site_instance_->RemoveObserver(this);
 
-  if (delegate_ && render_frame_created_)
+  const bool was_created = render_frame_created_;
+  render_frame_created_ = false;
+  if (delegate_ && was_created)
     delegate_->RenderFrameDeleted(this);
 
   // Ensure that the render process host has been notified that all audio
@@ -963,7 +965,7 @@
   // *always* first be unassociated from its corresponding RFHM. Thus, it
   // follows that |GetMainFrame()| will never return the speculative main frame
   // being deleted, since it must have already been unset.
-  if (render_frame_created_ && render_view_host_->GetMainFrame() != this)
+  if (was_created && render_view_host_->GetMainFrame() != this)
     CHECK(!is_active());
 
   GetProcess()->RemoveRoute(routing_id_);
@@ -5265,6 +5267,10 @@
   visual_state_callbacks_.emplace(key, std::move(callback));
 }
 
+bool RenderFrameHostImpl::IsRenderFrameCreated() {
+  return render_frame_created_;
+}
+
 bool RenderFrameHostImpl::IsRenderFrameLive() {
   bool is_live =
       GetProcess()->IsInitializedAndNotDead() && render_frame_created_;
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 15d0da85..2d937fc 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -242,6 +242,7 @@
   service_manager::InterfaceProvider* GetRemoteInterfaces() override;
   blink::AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() override;
   content::PageVisibilityState GetVisibilityState() override;
+  bool IsRenderFrameCreated() override;
   bool IsRenderFrameLive() override;
   bool IsCurrent() override;
   size_t GetProxyCount() override;
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index d65f88e..2af65f3 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -715,14 +715,21 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     SitePerProcessBrowserTest::SetUpCommandLine(command_line);
     ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true);
+    const char kParam[] = "provider";
+    std::map<std::string, std::string> parameters;
     if (std::get<0>(GetParam()) == 1) {
-      feature_list_.InitAndEnableFeature(features::kEnableVizHitTestDrawQuad);
+      parameters[kParam] = "draw_quad";
+      feature_list_.InitAndEnableFeatureWithParameters(
+          features::kEnableVizHitTest, parameters);
     } else if (std::get<0>(GetParam()) == 2) {
-      feature_list_.InitWithFeatures({features::kEnableVizHitTestSurfaceLayer},
-                                     {features::kEnableVizHitTestDrawQuad});
+      parameters[kParam] = "surface_layer";
+      feature_list_.InitAndEnableFeatureWithParameters(
+          features::kEnableVizHitTest, parameters);
     } else {
-      feature_list_.InitWithFeatures({}, {features::kEnableVizHitTestDrawQuad,
-                                          features::kVizDisplayCompositor});
+      feature_list_.InitWithFeatures(
+          {},
+          {features::kVizDisplayCompositor, features::kEnableVizHitTestDrawQuad,
+           features::kEnableVizHitTestSurfaceLayer});
     }
   }
 
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 76b89de..ab2e327 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -249,6 +249,11 @@
   // of a frame are defined in Blink.
   virtual PageVisibilityState GetVisibilityState() = 0;
 
+  // Returns true if WebContentsObserver::RenderFrameCreate notification has
+  // been dispatched for this frame, and so a RenderFrameDeleted notification
+  // will later be dispatched for this frame.
+  virtual bool IsRenderFrameCreated() = 0;
+
   // Returns whether the RenderFrame in the renderer process has been created
   // and still has a connection.  This is valid for all frames.
   virtual bool IsRenderFrameLive() = 0;
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index f26597e..2951268 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -690,9 +690,30 @@
                            static_cast<int32_t>(src.GetTextPosition()));
     }
 
-    if (src.TextStyle()) {
-      dst->AddIntAttribute(ax::mojom::IntAttribute::kTextStyle,
-                           src.TextStyle());
+    int32_t text_style = 0;
+    ax::mojom::TextDecorationStyle text_overline_style;
+    ax::mojom::TextDecorationStyle text_strikethrough_style;
+    ax::mojom::TextDecorationStyle text_underline_style;
+    src.GetTextStyleAndTextDecorationStyle(&text_style, &text_overline_style,
+                                           &text_strikethrough_style,
+                                           &text_underline_style);
+    if (text_style) {
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTextStyle, text_style);
+    }
+
+    if (text_overline_style != ax::mojom::TextDecorationStyle::kNone) {
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle,
+                           static_cast<int32_t>(text_overline_style));
+    }
+
+    if (text_strikethrough_style != ax::mojom::TextDecorationStyle::kNone) {
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle,
+                           static_cast<int32_t>(text_strikethrough_style));
+    }
+
+    if (text_underline_style != ax::mojom::TextDecorationStyle::kNone) {
+      dst->AddIntAttribute(ax::mojom::IntAttribute::kTextUnderlineStyle,
+                           static_cast<int32_t>(text_underline_style));
     }
 
     if (dst->role == ax::mojom::Role::kInlineTextBox) {
diff --git a/content/renderer/mus/renderer_window_tree_client.cc b/content/renderer/mus/renderer_window_tree_client.cc
index 3efa3d342..384141a 100644
--- a/content/renderer/mus/renderer_window_tree_client.cc
+++ b/content/renderer/mus/renderer_window_tree_client.cc
@@ -265,6 +265,7 @@
 void RendererWindowTreeClient::OnWindowBoundsChanged(
     ws::Id window_id,
     const gfx::Rect& new_bounds,
+    ui::WindowShowState state,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         local_surface_id_allocation) {}
 
diff --git a/content/renderer/mus/renderer_window_tree_client.h b/content/renderer/mus/renderer_window_tree_client.h
index 0938ed3..723e7fe 100644
--- a/content/renderer/mus/renderer_window_tree_client.h
+++ b/content/renderer/mus/renderer_window_tree_client.h
@@ -138,6 +138,7 @@
   void OnWindowBoundsChanged(
       ws::Id window_id,
       const gfx::Rect& new_bounds,
+      ui::WindowShowState state,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation) override;
   void OnWindowTransformChanged(ws::Id window_id,
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 987016a..f41433f 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -1339,7 +1339,7 @@
 
 void RenderWidget::EndUpdateLayers() {
   if (GetWebWidget())
-    GetWebWidget()->BeginUpdateLayers();
+    GetWebWidget()->EndUpdateLayers();
 }
 
 void RenderWidget::WillBeginCompositorFrame() {
diff --git a/content/test/data/accessibility/html/text-decoration-styles-expected-blink.txt b/content/test/data/accessibility/html/text-decoration-styles-expected-blink.txt
new file mode 100644
index 0000000..e1aee47
--- /dev/null
+++ b/content/test/data/accessibility/html/text-decoration-styles-expected-blink.txt
@@ -0,0 +1,55 @@
+rootWebArea
+++paragraph textOverlineStyle=solid
+++++staticText name='overline style: none' textOverlineStyle=solid
+++++++inlineTextBox name='overline style: none'
+++paragraph textOverlineStyle=dotted
+++++staticText name='overline style: dotted' textOverlineStyle=dotted
+++++++inlineTextBox name='overline style: dotted'
+++paragraph textOverlineStyle=dashed
+++++staticText name='overline style: dashed' textOverlineStyle=dashed
+++++++inlineTextBox name='overline style: dashed'
+++paragraph textOverlineStyle=solid
+++++staticText name='overline style: solid' textOverlineStyle=solid
+++++++inlineTextBox name='overline style: solid'
+++paragraph textOverlineStyle=double
+++++staticText name='overline style: double' textOverlineStyle=double
+++++++inlineTextBox name='overline style: double'
+++paragraph textOverlineStyle=wavy
+++++staticText name='overline style: wavy' textOverlineStyle=wavy
+++++++inlineTextBox name='overline style: wavy'
+++paragraph textUnderlineStyle=solid
+++++staticText name='underline style: none' textUnderlineStyle=solid
+++++++inlineTextBox name='underline style: none'
+++paragraph textUnderlineStyle=dotted
+++++staticText name='underline style: dotted' textUnderlineStyle=dotted
+++++++inlineTextBox name='underline style: dotted'
+++paragraph textUnderlineStyle=dashed
+++++staticText name='underline style: dashed' textUnderlineStyle=dashed
+++++++inlineTextBox name='underline style: dashed'
+++paragraph textUnderlineStyle=solid
+++++staticText name='underline style: solid' textUnderlineStyle=solid
+++++++inlineTextBox name='underline style: solid'
+++paragraph textUnderlineStyle=double
+++++staticText name='underline style: double' textUnderlineStyle=double
+++++++inlineTextBox name='underline style: double'
+++paragraph textUnderlineStyle=wavy
+++++staticText name='underline style: wavy' textUnderlineStyle=wavy
+++++++inlineTextBox name='underline style: wavy'
+++paragraph textStrikethroughStyle=solid
+++++staticText name='line-through style: none' textStrikethroughStyle=solid
+++++++inlineTextBox name='line-through style: none'
+++paragraph textStrikethroughStyle=dotted
+++++staticText name='line-through style: dotted' textStrikethroughStyle=dotted
+++++++inlineTextBox name='line-through style: dotted'
+++paragraph textStrikethroughStyle=dashed
+++++staticText name='line-through style: dashed' textStrikethroughStyle=dashed
+++++++inlineTextBox name='line-through style: dashed'
+++paragraph textStrikethroughStyle=solid
+++++staticText name='line-through style: solid' textStrikethroughStyle=solid
+++++++inlineTextBox name='line-through style: solid'
+++paragraph textStrikethroughStyle=double
+++++staticText name='line-through style: double' textStrikethroughStyle=double
+++++++inlineTextBox name='line-through style: double'
+++paragraph textStrikethroughStyle=wavy
+++++staticText name='line-through style: wavy' textStrikethroughStyle=wavy
+++++++inlineTextBox name='line-through style: wavy'
diff --git a/content/test/data/accessibility/html/text-decoration-styles.html b/content/test/data/accessibility/html/text-decoration-styles.html
new file mode 100644
index 0000000..2c9b3b3a
--- /dev/null
+++ b/content/test/data/accessibility/html/text-decoration-styles.html
@@ -0,0 +1,65 @@
+<!--
+@BLINK-ALLOW:textOverlineStyle*
+@BLINK-ALLOW:textUnderlineStyle*
+@BLINK-ALLOW:textStrikethroughStyle*
+-->
+<html>
+  <body>
+    <p style="text-decoration: overline; text-decoration-style: none;">
+      overline style: none
+    </p>
+    <p style="text-decoration: overline; text-decoration-style: dotted;">
+      overline style: dotted
+    </p>
+    <p style="text-decoration: overline; text-decoration-style: dashed;">
+      overline style: dashed
+    </p>
+    <p style="text-decoration: overline; text-decoration-style: solid;">
+      overline style: solid
+    </p>
+    <p style="text-decoration: overline; text-decoration-style: double;">
+      overline style: double
+    </p>
+    <p style="text-decoration: overline; text-decoration-style: wavy;">
+      overline style: wavy
+    </p>
+
+    <p style="text-decoration: underline; text-decoration-style: none;">
+      underline style: none
+    </p>
+    <p style="text-decoration: underline; text-decoration-style: dotted;">
+      underline style: dotted
+    </p>
+    <p style="text-decoration: underline; text-decoration-style: dashed;">
+      underline style: dashed
+    </p>
+    <p style="text-decoration: underline; text-decoration-style: solid;">
+      underline style: solid
+    </p>
+    <p style="text-decoration: underline; text-decoration-style: double;">
+      underline style: double
+    </p>
+    <p style="text-decoration: underline; text-decoration-style: wavy;">
+      underline style: wavy
+    </p>
+
+    <p style="text-decoration: line-through; text-decoration-style: none;">
+      line-through style: none
+    </p>
+    <p style="text-decoration: line-through; text-decoration-style: dotted;">
+      line-through style: dotted
+    </p>
+    <p style="text-decoration: line-through; text-decoration-style: dashed;">
+      line-through style: dashed
+    </p>
+    <p style="text-decoration: line-through; text-decoration-style: solid;">
+      line-through style: solid
+    </p>
+    <p style="text-decoration: line-through; text-decoration-style: double;">
+      line-through style: double
+    </p>
+    <p style="text-decoration: line-through; text-decoration-style: wavy;">
+      line-through style: wavy
+    </p>
+  </body>
+</html>
diff --git a/content/test/web_contents_observer_sanity_checker.cc b/content/test/web_contents_observer_sanity_checker.cc
index e1c6276..97cab3b8 100644
--- a/content/test/web_contents_observer_sanity_checker.cc
+++ b/content/test/web_contents_observer_sanity_checker.cc
@@ -55,12 +55,14 @@
                  << Format(render_frame_host);
   }
 
+  CHECK(render_frame_host->IsRenderFrameCreated())
+      << "RenderFrameCreated was called for a RenderFrameHost that has not been"
+         "marked created.";
   CHECK(render_frame_host->GetProcess()->IsInitializedAndNotDead())
       << "RenderFrameCreated was called for a RenderFrameHost whose render "
          "process is not currently live, so there's no way for the RenderFrame "
          "to have been created.";
-  CHECK(
-      static_cast<RenderFrameHostImpl*>(render_frame_host)->IsRenderFrameLive())
+  CHECK(render_frame_host->IsRenderFrameLive())
       << "RenderFrameCreated called on for a RenderFrameHost that thinks it is "
          "not alive.";
 
@@ -81,6 +83,13 @@
 void WebContentsObserverSanityChecker::RenderFrameDeleted(
     RenderFrameHost* render_frame_host) {
   CHECK(!web_contents_destroyed_);
+  CHECK(!render_frame_host->IsRenderFrameCreated())
+      << "RenderFrameDeleted was called for a RenderFrameHost that is"
+         "(still) marked as created.";
+  CHECK(!render_frame_host->IsRenderFrameLive())
+      << "RenderFrameDeleted was called for a RenderFrameHost that is"
+         "still live.";
+
   GlobalRoutingID routing_pair = GetRoutingPair(render_frame_host);
   bool was_live = !!live_routes_.erase(routing_pair);
   bool was_dead_already = !deleted_routes_.insert(routing_pair).second;
diff --git a/device/gamepad/dualshock4_controller_linux.cc b/device/gamepad/dualshock4_controller_linux.cc
index 2787d38..d13368f 100644
--- a/device/gamepad/dualshock4_controller_linux.cc
+++ b/device/gamepad/dualshock4_controller_linux.cc
@@ -8,7 +8,8 @@
 
 namespace device {
 
-Dualshock4ControllerLinux::Dualshock4ControllerLinux(int fd) : fd_(fd) {}
+Dualshock4ControllerLinux::Dualshock4ControllerLinux(const base::ScopedFD& fd)
+    : fd_(fd.get()) {}
 
 Dualshock4ControllerLinux::~Dualshock4ControllerLinux() = default;
 
diff --git a/device/gamepad/dualshock4_controller_linux.h b/device/gamepad/dualshock4_controller_linux.h
index dfc0bde..82b159c51 100644
--- a/device/gamepad/dualshock4_controller_linux.h
+++ b/device/gamepad/dualshock4_controller_linux.h
@@ -7,16 +7,19 @@
 
 #include "device/gamepad/dualshock4_controller_base.h"
 
+#include "base/files/scoped_file.h"
+
 namespace device {
 
 class Dualshock4ControllerLinux : public Dualshock4ControllerBase {
  public:
-  Dualshock4ControllerLinux(int fd);
+  Dualshock4ControllerLinux(const base::ScopedFD& fd);
   ~Dualshock4ControllerLinux() override;
 
   size_t WriteOutputReport(void* report, size_t report_length) override;
 
  private:
+  // Not owned.
   int fd_;
 };
 
diff --git a/device/gamepad/gamepad_device_linux.cc b/device/gamepad/gamepad_device_linux.cc
index c18f8b5..9c60275 100644
--- a/device/gamepad/gamepad_device_linux.cc
+++ b/device/gamepad/gamepad_device_linux.cc
@@ -54,9 +54,9 @@
   return data[bit / LONG_BITS] & (1UL << (bit % LONG_BITS));
 }
 
-GamepadBusType GetEvdevBusType(int fd) {
+GamepadBusType GetEvdevBusType(const base::ScopedFD& fd) {
   struct input_id input_info;
-  if (HANDLE_EINTR(ioctl(fd, EVIOCGID, &input_info)) >= 0) {
+  if (HANDLE_EINTR(ioctl(fd.get(), EVIOCGID, &input_info)) >= 0) {
     if (input_info.bustype == BUS_USB)
       return GAMEPAD_BUS_USB;
     if (input_info.bustype == BUS_BLUETOOTH)
@@ -65,12 +65,12 @@
   return GAMEPAD_BUS_UNKNOWN;
 }
 
-bool HasRumbleCapability(int fd) {
+bool HasRumbleCapability(const base::ScopedFD& fd) {
   unsigned long evbit[BITS_TO_LONGS(EV_MAX)];
   unsigned long ffbit[BITS_TO_LONGS(FF_MAX)];
 
-  if (HANDLE_EINTR(ioctl(fd, EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
-      HANDLE_EINTR(ioctl(fd, EVIOCGBIT(EV_FF, FF_MAX), ffbit)) < 0) {
+  if (HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
+      HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(EV_FF, FF_MAX), ffbit)) < 0) {
     return false;
   }
 
@@ -85,15 +85,16 @@
 // aren't reported by joydev. If a special key is found, the corresponding entry
 // of the |has_special_key| vector is set to true. Returns the number of
 // special keys found.
-size_t CheckSpecialKeys(int fd, std::vector<bool>* has_special_key) {
+size_t CheckSpecialKeys(const base::ScopedFD& fd,
+                        std::vector<bool>* has_special_key) {
   DCHECK(has_special_key);
   unsigned long evbit[BITS_TO_LONGS(EV_MAX)];
   unsigned long keybit[BITS_TO_LONGS(KEY_MAX)];
   size_t found_special_keys = 0;
 
   has_special_key->clear();
-  if (HANDLE_EINTR(ioctl(fd, EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
-      HANDLE_EINTR(ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), keybit)) < 0) {
+  if (HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(0, EV_MAX), evbit)) < 0 ||
+      HANDLE_EINTR(ioctl(fd.get(), EVIOCGBIT(EV_KEY, KEY_MAX), keybit)) < 0) {
     return 0;
   }
 
@@ -112,12 +113,12 @@
   return found_special_keys;
 }
 
-bool GetHidrawDevinfo(int fd,
+bool GetHidrawDevinfo(const base::ScopedFD& fd,
                       GamepadBusType* bus_type,
                       uint16_t* vendor_id,
                       uint16_t* product_id) {
   struct hidraw_devinfo info;
-  if (HANDLE_EINTR(ioctl(fd, HIDIOCGRAWINFO, &info)) < 0)
+  if (HANDLE_EINTR(ioctl(fd.get(), HIDIOCGRAWINFO, &info)) < 0)
     return false;
   if (bus_type) {
     if (info.bustype == BUS_USB)
@@ -134,7 +135,7 @@
   return true;
 }
 
-int StoreRumbleEffect(int fd,
+int StoreRumbleEffect(const base::ScopedFD& fd,
                       int effect_id,
                       uint16_t duration,
                       uint16_t start_delay,
@@ -149,23 +150,23 @@
   effect.u.rumble.strong_magnitude = strong_magnitude;
   effect.u.rumble.weak_magnitude = weak_magnitude;
 
-  if (HANDLE_EINTR(ioctl(fd, EVIOCSFF, (const void*)&effect)) < 0)
+  if (HANDLE_EINTR(ioctl(fd.get(), EVIOCSFF, (const void*)&effect)) < 0)
     return kInvalidEffectId;
   return effect.id;
 }
 
-void DestroyEffect(int fd, int effect_id) {
-  HANDLE_EINTR(ioctl(fd, EVIOCRMFF, effect_id));
+void DestroyEffect(const base::ScopedFD& fd, int effect_id) {
+  HANDLE_EINTR(ioctl(fd.get(), EVIOCRMFF, effect_id));
 }
 
-bool StartOrStopEffect(int fd, int effect_id, bool do_start) {
+bool StartOrStopEffect(const base::ScopedFD& fd, int effect_id, bool do_start) {
   struct input_event start_stop;
   memset(&start_stop, 0, sizeof(start_stop));
   start_stop.type = EV_FF;
   start_stop.code = effect_id;
   start_stop.value = do_start ? 1 : 0;
-  ssize_t nbytes =
-      HANDLE_EINTR(write(fd, (const void*)&start_stop, sizeof(start_stop)));
+  ssize_t nbytes = HANDLE_EINTR(
+      write(fd.get(), (const void*)&start_stop, sizeof(start_stop)));
   return nbytes == sizeof(start_stop);
 }
 
@@ -198,18 +199,19 @@
 }
 
 bool GamepadDeviceLinux::IsEmpty() const {
-  return joydev_fd_ < 0 && evdev_fd_ < 0 && hidraw_fd_ < 0;
+  return !joydev_fd_.is_valid() && !evdev_fd_.is_valid() &&
+         !hidraw_fd_.is_valid();
 }
 
 bool GamepadDeviceLinux::SupportsVibration() const {
   if (dualshock4_ || hid_haptics_)
     return true;
 
-  return supports_force_feedback_ && evdev_fd_ >= 0;
+  return supports_force_feedback_ && evdev_fd_.is_valid();
 }
 
 void GamepadDeviceLinux::ReadPadState(Gamepad* pad) {
-  DCHECK_GE(joydev_fd_, 0);
+  DCHECK(joydev_fd_.is_valid());
 
   // Read button and axis events from the joydev device.
   bool pad_updated = ReadJoydevState(pad);
@@ -234,13 +236,14 @@
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
   DCHECK(pad);
 
-  if (joydev_fd_ < 0)
+  if (!joydev_fd_.is_valid())
     return false;
 
   // Read button and axis events from the joydev device.
   bool pad_updated = false;
   js_event event;
-  while (HANDLE_EINTR(read(joydev_fd_, &event, sizeof(struct js_event))) > 0) {
+  while (HANDLE_EINTR(read(joydev_fd_.get(), &event, sizeof(struct js_event))) >
+         0) {
     size_t item = event.number;
     if (event.type & JS_EVENT_AXIS) {
       if (item >= Gamepad::kAxesLengthCap)
@@ -274,7 +277,7 @@
 
 void GamepadDeviceLinux::InitializeEvdevSpecialKeys() {
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
-  if (evdev_fd_ < 0)
+  if (!evdev_fd_.is_valid())
     return;
 
   // Do some one-time initialization to decide indices for the evdev special
@@ -316,15 +319,15 @@
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
   DCHECK(pad);
 
-  if (evdev_fd_ < 0)
+  if (!evdev_fd_.is_valid())
     return false;
 
   // Read special button events through evdev.
   bool pad_updated = false;
   input_event ev;
   ssize_t bytes_read;
-  while ((bytes_read =
-              HANDLE_EINTR(read(evdev_fd_, &ev, sizeof(input_event)))) > 0) {
+  while ((bytes_read = HANDLE_EINTR(
+              read(evdev_fd_.get(), &ev, sizeof(input_event)))) > 0) {
     if (size_t{bytes_read} < sizeof(input_event))
       break;
     if (ev.type != EV_KEY)
@@ -362,8 +365,9 @@
   DCHECK(pad_info.syspath_prefix == syspath_prefix_);
 
   CloseJoydevNode();
-  joydev_fd_ = open(pad_info.path.c_str(), O_RDONLY | O_NONBLOCK);
-  if (joydev_fd_ < 0)
+  joydev_fd_ =
+      base::ScopedFD(open(pad_info.path.c_str(), O_RDONLY | O_NONBLOCK));
+  if (!joydev_fd_.is_valid())
     return false;
 
   udev_device* parent_device =
@@ -422,10 +426,7 @@
 
 void GamepadDeviceLinux::CloseJoydevNode() {
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
-  if (joydev_fd_ >= 0) {
-    close(joydev_fd_);
-    joydev_fd_ = -1;
-  }
+  joydev_fd_.reset();
   joydev_index_ = -1;
   vendor_id_ = 0;
   product_id_ = 0;
@@ -444,8 +445,8 @@
   DCHECK(pad_info.syspath_prefix == syspath_prefix_);
 
   CloseEvdevNode();
-  evdev_fd_ = open(pad_info.path.c_str(), O_RDWR | O_NONBLOCK);
-  if (evdev_fd_ < 0)
+  evdev_fd_ = base::ScopedFD(open(pad_info.path.c_str(), O_RDWR | O_NONBLOCK));
+  if (!evdev_fd_.is_valid())
     return false;
 
   supports_force_feedback_ = HasRumbleCapability(evdev_fd_);
@@ -456,14 +457,13 @@
 
 void GamepadDeviceLinux::CloseEvdevNode() {
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
-  if (evdev_fd_ >= 0) {
+  if (evdev_fd_.is_valid()) {
     if (effect_id_ != kInvalidEffectId) {
       DestroyEffect(evdev_fd_, effect_id_);
       effect_id_ = kInvalidEffectId;
     }
-    close(evdev_fd_);
-    evdev_fd_ = -1;
   }
+  evdev_fd_.reset();
   supports_force_feedback_ = false;
 
   // Clear any entries in |button_indices_used_| that were taken by evdev.
@@ -520,7 +520,7 @@
 void GamepadDeviceLinux::InitializeHidraw(base::ScopedFD fd) {
   DCHECK(polling_runner_->RunsTasksInCurrentSequence());
   DCHECK(fd.is_valid());
-  hidraw_fd_ = fd.release();
+  hidraw_fd_ = std::move(fd);
 
   uint16_t vendor_id;
   uint16_t product_id;
@@ -550,10 +550,7 @@
   if (hid_haptics_)
     hid_haptics_->Shutdown();
   hid_haptics_.reset();
-  if (hidraw_fd_ >= 0) {
-    close(hidraw_fd_);
-    hidraw_fd_ = -1;
-  }
+  hidraw_fd_.reset();
 }
 
 #if defined(OS_CHROMEOS)
diff --git a/device/gamepad/gamepad_device_linux.h b/device/gamepad/gamepad_device_linux.h
index c47c927..3254158 100644
--- a/device/gamepad/gamepad_device_linux.h
+++ b/device/gamepad/gamepad_device_linux.h
@@ -28,8 +28,8 @@
 // evdev interface. A gamepad must be enumerated through joydev to be usable,
 // but the evdev interface is only required for haptic effects.
 //
-// Dualshock4 haptics are not supported through evdev and are instead sent
-// through the raw HID (hidraw) interface.
+// For some devices, haptics are not supported through evdev and are instead
+// sent through the raw HID (hidraw) interface.
 class GamepadDeviceLinux : public AbstractHapticGamepad {
  public:
   using OpenDeviceNodeCallback = base::OnceCallback<void(GamepadDeviceLinux*)>;
@@ -124,9 +124,8 @@
   // the syspath prefix up to the subsystem.
   std::string syspath_prefix_;
 
-  // The file descriptor for the device's joydev node, or -1 if no joydev node
-  // is associated with this device.
-  int joydev_fd_ = -1;
+  // The file descriptor for the device's joydev node.
+  base::ScopedFD joydev_fd_;
 
   // The index of the device's joydev node, or -1 if unknown.
   // The joydev index is the integer at the end of the joydev node path and is
@@ -150,9 +149,8 @@
   // A string identifying the manufacturer and model of the device.
   std::string name_;
 
-  // The file descriptor for the device's evdev node, or -1 if no evdev node is
-  // associated with this device.
-  int evdev_fd_ = -1;
+  // The file descriptor for the device's evdev node.
+  base::ScopedFD evdev_fd_;
 
   // The ID of the haptic effect stored on the device, or -1 if none is stored.
   int effect_id_ = -1;
@@ -168,9 +166,8 @@
   // button is not mapped. Empty if no special buttons are mapped.
   std::vector<int> special_button_map_;
 
-  // The file descriptor for the device's hidraw node, or -1 if no hidraw node
-  // is associated with this device.
-  int hidraw_fd_ = -1;
+  // The file descriptor for the device's hidraw node.
+  base::ScopedFD hidraw_fd_;
 
   // The type of the bus through which the device is connected, or
   // GAMEPAD_BUS_UNKNOWN if the bus type could not be determined.
diff --git a/device/gamepad/hid_haptic_gamepad_linux.cc b/device/gamepad/hid_haptic_gamepad_linux.cc
index d0680c9..cbc53b3 100644
--- a/device/gamepad/hid_haptic_gamepad_linux.cc
+++ b/device/gamepad/hid_haptic_gamepad_linux.cc
@@ -8,15 +8,17 @@
 
 namespace device {
 
-HidHapticGamepadLinux::HidHapticGamepadLinux(int fd,
+HidHapticGamepadLinux::HidHapticGamepadLinux(const base::ScopedFD& fd,
                                              const HapticReportData& data)
-    : HidHapticGamepadBase(data), fd_(fd) {}
+    : HidHapticGamepadBase(data), fd_(fd.get()) {}
 
 HidHapticGamepadLinux::~HidHapticGamepadLinux() = default;
 
 // static
-std::unique_ptr<HidHapticGamepadLinux>
-HidHapticGamepadLinux::Create(uint16_t vendor_id, uint16_t product_id, int fd) {
+std::unique_ptr<HidHapticGamepadLinux> HidHapticGamepadLinux::Create(
+    uint16_t vendor_id,
+    uint16_t product_id,
+    const base::ScopedFD& fd) {
   const auto* haptic_data = GetHapticReportData(vendor_id, product_id);
   if (!haptic_data)
     return nullptr;
diff --git a/device/gamepad/hid_haptic_gamepad_linux.h b/device/gamepad/hid_haptic_gamepad_linux.h
index fa3c9a04..6656a740 100644
--- a/device/gamepad/hid_haptic_gamepad_linux.h
+++ b/device/gamepad/hid_haptic_gamepad_linux.h
@@ -9,20 +9,22 @@
 
 #include <memory>
 
+#include "base/files/scoped_file.h"
+
 namespace device {
 
 class HidHapticGamepadLinux : public HidHapticGamepadBase {
  public:
-  HidHapticGamepadLinux(int fd, const HapticReportData& data);
+  HidHapticGamepadLinux(const base::ScopedFD& fd, const HapticReportData& data);
   ~HidHapticGamepadLinux() override;
 
-  static std::unique_ptr<HidHapticGamepadLinux> Create(uint16_t vendor_id,
-                                                       uint16_t product_id,
-                                                       int fd);
+  static std::unique_ptr<HidHapticGamepadLinux>
+  Create(uint16_t vendor_id, uint16_t product_id, const base::ScopedFD& fd);
 
   size_t WriteOutputReport(void* report, size_t report_length) override;
 
  private:
+  // Not owned.
   int fd_;
 };
 
diff --git a/extensions/browser/api/feedback_private/feedback_private_api.cc b/extensions/browser/api/feedback_private/feedback_private_api.cc
index eb70419..c80b292 100644
--- a/extensions/browser/api/feedback_private/feedback_private_api.cc
+++ b/extensions/browser/api/feedback_private/feedback_private_api.cc
@@ -22,6 +22,7 @@
 #include "base/system/sys_info.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "components/feedback/feedback_report.h"
 #include "components/feedback/system_logs/system_logs_fetcher.h"
 #include "components/feedback/tracing_manager.h"
 #include "extensions/browser/api/extensions_api_client.h"
@@ -60,6 +61,11 @@
 
 constexpr char kBluetoothLogsAttachmentName[] = "bluetooth_logs.bz2";
 
+bool IsGoogleEmail(const std::string& email) {
+  return base::EndsWith(email, "@google.com",
+                        base::CompareCase::INSENSITIVE_ASCII);
+}
+
 // Getting the filename of a blob prepends a "C:\fakepath" to the filename.
 // This is undesirable, strip it if it exists.
 std::string StripFakepath(const std::string& path) {
@@ -79,10 +85,8 @@
   if (board.find("eve") == std::string::npos)
     return feedback_private::LANDING_PAGE_TYPE_NORMAL;
 
-  if (!base::EndsWith(email, "@google.com",
-                      base::CompareCase::INSENSITIVE_ASCII)) {
+  if (!IsGoogleEmail(email))
     return feedback_private::LANDING_PAGE_TYPE_NORMAL;
-  }
 
   return feedback_private::LANDING_PAGE_TYPE_TECHSTOP;
 #else
@@ -229,7 +233,21 @@
   SystemInformationList sys_info_list;
   if (sys_info) {
     sys_info_list.reserve(sys_info->size());
+    const bool google_email =
+        IsGoogleEmail(ExtensionsAPIClient::Get()
+                          ->GetFeedbackPrivateDelegate()
+                          ->GetSignedInUserEmail(browser_context()));
     for (auto& itr : *sys_info) {
+      // We only send the list of all the crash report IDs if the user has a
+      // @google.com email. We strip this here so that the system information
+      // view properly reflects what we will be uploading to the server. It is
+      // also stripped later on in the feedback processing for other code paths
+      // that don't go through this.
+      if (itr.first == feedback::FeedbackReport::kAllCrashReportIdsKey &&
+          !google_email) {
+        continue;
+      }
+
       SystemInformation sys_info_entry;
       sys_info_entry.key = std::move(itr.first);
       sys_info_entry.value = std::move(itr.second);
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 1d5ae26..eb50f9f 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -757,8 +757,9 @@
   auto authentication_request = mojo::MakeRequest(auth_handler);
 
   network::mojom::TrustedHeaderClientRequest header_client_request;
-  if (ExtensionWebRequestEventRouter::GetInstance()->HasAnyExtraHeadersListener(
-          frame->GetProcess()->GetBrowserContext())) {
+  if (ExtensionWebRequestEventRouter::GetInstance()
+          ->HasAnyExtraHeadersListenerOnUI(
+              frame->GetProcess()->GetBrowserContext())) {
     header_client_request = mojo::MakeRequest(header_client);
   }
 
@@ -1669,9 +1670,19 @@
 
   listeners_[browser_context][event_name].push_back(std::move(listener));
 
-  if (extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS)
+  if (extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS) {
+    bool had_previously = extra_headers_listener_count_[browser_context] > 0;
     extra_headers_listener_count_[browser_context]++;
 
+    if (!had_previously) {
+      base::PostTaskWithTraits(
+          FROM_HERE, {BrowserThread::UI},
+          base::BindOnce(
+              &ExtensionWebRequestEventRouter::UpdateExtraHeadersListenerOnUI,
+              base::Unretained(this), browser_context, true));
+    }
+  }
+
   return true;
 }
 
@@ -1729,6 +1740,15 @@
         extra_headers_listener_count_[listener->id.browser_context]--;
         DCHECK_GE(extra_headers_listener_count_[listener->id.browser_context],
                   0);
+
+        if (extra_headers_listener_count_[listener->id.browser_context] == 0) {
+          base::PostTaskWithTraits(
+              FROM_HERE, {BrowserThread::UI},
+              base::BindOnce(&ExtensionWebRequestEventRouter::
+                                 UpdateExtraHeadersListenerOnUI,
+                             base::Unretained(this),
+                             listener->id.browser_context, false));
+        }
       }
 
       listeners.erase(it);
@@ -1800,6 +1820,7 @@
 
 bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListener(
     void* browser_context) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   if (HasAnyExtraHeadersListenerImpl(browser_context))
     return true;
 
@@ -1810,11 +1831,42 @@
   return false;
 }
 
+bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListenerOnUI(
+    content::BrowserContext* browser_context) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (browser_contexts_with_extra_headers_.find(browser_context) !=
+      browser_contexts_with_extra_headers_.end())
+    return true;
+
+  if (browser_context->IsOffTheRecord()) {
+    auto* original_browser_context =
+        ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context);
+    if (browser_contexts_with_extra_headers_.find(original_browser_context) !=
+        browser_contexts_with_extra_headers_.end())
+      return true;
+  }
+
+  return false;
+}
+
 bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListenerImpl(
     void* browser_context) {
   return extra_headers_listener_count_[browser_context] > 0;
 }
 
+void ExtensionWebRequestEventRouter::UpdateExtraHeadersListenerOnUI(
+    void* browser_context,
+    bool has_extra_headers_listeners) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  auto* browser_context_ptr =
+      static_cast<content::BrowserContext*>(browser_context);
+  if (has_extra_headers_listeners) {
+    browser_contexts_with_extra_headers_.insert(browser_context_ptr);
+  } else {
+    browser_contexts_with_extra_headers_.erase(browser_context_ptr);
+  }
+}
+
 bool ExtensionWebRequestEventRouter::IsPageLoad(
     const WebRequestInfo& request) const {
   return request.type == content::RESOURCE_TYPE_MAIN_FRAME;
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h
index bd9ea87..ac06e82 100644
--- a/extensions/browser/api/web_request/web_request_api.h
+++ b/extensions/browser/api/web_request/web_request_api.h
@@ -502,6 +502,9 @@
   // ExtraInfoSpec::EXTRA_HEADERS set.
   bool HasAnyExtraHeadersListener(void* browser_context);
 
+  // Like above, but for usage on the UI thread.
+  bool HasAnyExtraHeadersListenerOnUI(content::BrowserContext* browser_context);
+
  private:
   friend class WebRequestAPI;
   friend class base::NoDestructor<ExtensionWebRequestEventRouter>;
@@ -731,6 +734,10 @@
   // Helper for |HasAnyExtraHeadersListener()|.
   bool HasAnyExtraHeadersListenerImpl(void* browser_context);
 
+  // Called on the UI thread to update |browser_contexts_with_extra_headers_|.
+  void UpdateExtraHeadersListenerOnUI(void* browser_context,
+                                      bool has_extra_headers_listeners);
+
   // Get the number of listeners - for testing only.
   size_t GetListenerCountForTesting(void* browser_context,
                                     const std::string& event_name);
@@ -745,6 +752,10 @@
   // Count of listeners per browser context which request extra headers.
   ExtraHeadersListenerCountMap extra_headers_listener_count_;
 
+  // Accessed on the UI thread to check if a given BrowserContext has any
+  // extra headers listeners.
+  std::set<content::BrowserContext*> browser_contexts_with_extra_headers_;
+
   // A map of network requests that are waiting for at least one event handler
   // to respond.
   BlockedRequestMap blocked_requests_;
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc
index adafc8c..1a42885 100644
--- a/extensions/browser/url_loader_factory_manager.cc
+++ b/extensions/browser/url_loader_factory_manager.cc
@@ -102,6 +102,8 @@
     "3787567233ED6BACC4FC05387BE30E03434CE4CC",
     "37AC33A3A46D271CCE57DD6CB3FACE6B01F5A347",
     "38B4D1CC339580F506BC86D4027A49721AFB4BB9",
+    "395D84F94DA287C0E4DBAF9ACE478B9710C0029F",
+    "3A2E664CA697C622EA4CFA40373B3E641C01713B",
     "3BC834B48C2C13765147FBAD710F792F026378D8",
     "3CD98763C80D86E00CB1C4CAA56CEA8F3B0BA4F1",
     "3EB17C39F8B6B28FAF34E2406E57A76013A2E066",
@@ -121,6 +123,7 @@
     "4E4167EDA0CFF22F261C0655E979B9474BF67C04",
     "5053323D1F7B6EEC97A77A350DB6D0D8E51CD0AC",
     "505F2C1E723731B2C8C9182FEAA73D00525B2048",
+    "505F3697087BFD3F290F42D029CE67F1C793B2DA",
     "50DDD8734521B61564FCE273F8E60547F88BBCBE",
     "52865B2087D0ABCD195A83DFD4BD041A3B4EBC34",
     "52C94AC7680C3A03CCB6EA31445DD42BD0D5CA8E",
@@ -175,6 +178,7 @@
     "973E35633030AD27DABEC99609424A61386C7309",
     "97E04C5632954E778306CAC40B3F95C470B463B6",
     "98EF7B1601119AEE1FCC28EE5CE247DED5676539",
+    "999BD8D1929F9ABB817E9368480D93BAB2A0983D",
     "99E06C364BBB2D1F82A9D20BC1645BF21E478259",
     "9C6A186F8D3C5FD0CC8DCF49682FA726BD8A7705",
     "A04F08A772F1C83B7A14ED29788ACA4F000BBE05",
diff --git a/gpu/command_buffer/client/shared_image_interface.h b/gpu/command_buffer/client/shared_image_interface.h
index 4f41238d..8b80c01 100644
--- a/gpu/command_buffer/client/shared_image_interface.h
+++ b/gpu/command_buffer/client/shared_image_interface.h
@@ -34,8 +34,8 @@
   // API(s) the image will be used with.
   // Returns a mailbox that can be imported into said APIs using their
   // corresponding shared image functions (e.g.
-  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM) or (deprecated)
-  // mailbox functions (e.g.  RasterInterface::CreateAndConsumeTexture or
+  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM or
+  // RasterInterface::CopySubTexture) or (deprecated) mailbox functions (e.g.
   // GLES2Interface::CreateAndConsumeTextureCHROMIUM).
   // The |SharedImageInterface| keeps ownership of the image until
   // |DestroySharedImage| is called or the interface itself is destroyed (e.g.
@@ -59,12 +59,15 @@
   // |usage| is a combination of |SharedImageUsage| bits that describes which
   // API(s) the image will be used with. Format and size are derived from the
   // GpuMemoryBuffer. |gpu_memory_buffer_manager| is the manager that created
-  // |gpu_memory_buffer|. If valid, |color_space| will be applied to the shared
+  // |gpu_memory_buffer|. If the |gpu_memory_buffer| was created on the client
+  // side (for NATIVE_PIXMAP or ANDROID_HARDWARE_BUFFER types only), without a
+  // GpuMemoryBufferManager, |gpu_memory_buffer_manager| can be nullptr.
+  // If valid, |color_space| will be applied to the shared
   // image (possibly overwriting the one set on the GpuMemoryBuffer).
   // Returns a mailbox that can be imported into said APIs using their
   // corresponding shared image functions (e.g.
-  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM) or (deprecated)
-  // mailbox functions (e.g.  RasterInterface::CreateAndConsumeTexture or
+  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM or
+  // RasterInterface::CopySubTexture) or (deprecated) mailbox functions (e.g.
   // GLES2Interface::CreateAndConsumeTextureCHROMIUM).
   // The |SharedImageInterface| keeps ownership of the image until
   // |DestroySharedImage| is called or the interface itself is destroyed (e.g.
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index eb476fd..896da60 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -2178,7 +2178,7 @@
     // hangs.
     gl::ScopedProgressReporter report_progress(
         shared_context_state_->progress_reporter());
-    sk_surface_->prepareForExternalIO();
+    sk_surface_->flush();
   }
 
   if (!shared_image_) {
@@ -2190,7 +2190,7 @@
   }
 
   // Unlock all font handles. This needs to be deferred until
-  // SkSurface::prepareForExternalIO since that flushes batched Gr operations
+  // SkSurface::flush since that flushes batched Gr operations
   // in skia that access the glyph data.
   // TODO(khushalsagar): We just unlocked a bunch of handles, do we need to
   // give a call to skia to attempt to purge any unlocked handles?
@@ -2201,7 +2201,7 @@
   locked_handles_.clear();
 
   // We just flushed a tile's worth of GPU work from the SkSurface in
-  // prepareForExternalIO above. Use kDeferLaterCommands to ensure we yield to
+  // flush above. Use kDeferLaterCommands to ensure we yield to
   // the Scheduler before processing more commands.
   current_decoder_error_ = error::kDeferLaterCommands;
 }
diff --git a/gpu/ipc/client/shared_image_interface_proxy.cc b/gpu/ipc/client/shared_image_interface_proxy.cc
index 3adb4b3..db7e9432 100644
--- a/gpu/ipc/client/shared_image_interface_proxy.cc
+++ b/gpu/ipc/client/shared_image_interface_proxy.cc
@@ -123,7 +123,9 @@
     GpuMemoryBufferManager* gpu_memory_buffer_manager,
     const gfx::ColorSpace& color_space,
     uint32_t usage) {
-  DCHECK(gpu_memory_buffer_manager);
+  DCHECK(gpu_memory_buffer->GetType() == gfx::NATIVE_PIXMAP ||
+         gpu_memory_buffer->GetType() == gfx::ANDROID_HARDWARE_BUFFER ||
+         gpu_memory_buffer_manager);
   GpuChannelMsg_CreateGMBSharedImage_Params params;
   params.mailbox = Mailbox::GenerateForSharedImage();
   params.handle = gpu_memory_buffer->CloneHandle();
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index 375d7184..623895c 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -173,7 +173,9 @@
                             GpuMemoryBufferManager* gpu_memory_buffer_manager,
                             const gfx::ColorSpace& color_space,
                             uint32_t usage) override {
-    DCHECK(gpu_memory_buffer_manager);
+    DCHECK(gpu_memory_buffer->GetType() == gfx::NATIVE_PIXMAP ||
+           gpu_memory_buffer->GetType() == gfx::ANDROID_HARDWARE_BUFFER ||
+           gpu_memory_buffer_manager);
 
     // TODO(piman): DCHECK GMB format support.
     DCHECK(gpu::IsImageSizeValidForGpuMemoryBufferFormat(
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index 92d57e5e..92faf6e 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -598,6 +598,13 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          autofill::features::kAutofillImportNonFocusableCreditCardForms)},
+    {"enable-autofill-do-not-upload-save-unsupported-cards",
+     flag_descriptions::kEnableAutofillDoNotUploadSaveUnsupportedCardsName,
+     flag_descriptions::
+         kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillDoNotUploadSaveUnsupportedCards)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index e6c7d64..bfec0c9 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -68,6 +68,12 @@
     "If enabled, changes the server save card prompt's explanation to mention "
     "the saving of the billing address.";
 
+const char kEnableAutofillDoNotUploadSaveUnsupportedCardsName[] =
+    "Prevents upload save on cards from unsupported networks";
+const char kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription[] =
+    "If enabled, cards from unsupported networks will not be offered upload "
+    "save, and will instead be offered local save.";
+
 const char kEnableAutofillSaveCreditCardUsesStrikeSystemName[] =
     "Enable limit on offering to save the same credit card repeatedly";
 const char kEnableAutofillSaveCreditCardUsesStrikeSystemDescription[] =
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index e2a6e475..628c376 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -55,6 +55,11 @@
 extern const char
     kEnableAutofillCreditCardUploadUpdatePromptExplanationDescription[];
 
+// Title and description for the flag to control if cards from unsupported
+// networks should be prevented form being uploaded.
+extern const char kEnableAutofillDoNotUploadSaveUnsupportedCardsName[];
+extern const char kEnableAutofillDoNotUploadSaveUnsupportedCardsDescription[];
+
 // Title and description for the flag to control if credit card save should
 // utilize the Autofill StrikeDatabase when determining whether save
 // should be offered.
diff --git a/ios/testing/earl_grey/earl_grey_app.h b/ios/testing/earl_grey/earl_grey_app.h
index 4f9facc..b74efd5 100644
--- a/ios/testing/earl_grey/earl_grey_app.h
+++ b/ios/testing/earl_grey/earl_grey_app.h
@@ -18,8 +18,10 @@
 #elif defined(CHROME_EARL_GREY_2)
 
 #import <AppFramework/Action/GREYActionsShorthand.h>
+#import <AppFramework/Core/GREYElementInteraction.h>
 #import <AppFramework/EarlGreyApp.h>
 #import <AppFramework/Matcher/GREYMatchersShorthand.h>
+#import <CommonLib/Error/GREYErrorConstants.h>
 
 #else
 #error Must define either CHROME_EARL_GREY_1 or CHROME_EARL_GREY_2.
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 77ece4f..82da640 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -104,6 +104,7 @@
 }
 
 source_set("earl_grey_test_support") {
+  defines = [ "CHROME_EARL_GREY_1" ]
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
 
@@ -129,6 +130,32 @@
   ]
 }
 
+source_set("eg_app_support+eg2") {
+  defines = [ "CHROME_EARL_GREY_2" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+
+  deps = [
+    ":web",
+    "//base",
+    "//base/test:test_support",
+    "//ios/testing/earl_grey:eg_app_support+eg2",
+    "//ios/third_party/earl_grey2:app_framework+link",
+    "//ios/web/interstitials",
+    "//ios/web/public/test",
+    "//net",
+  ]
+
+  sources = [
+    "public/test/earl_grey/js_test_util.h",
+    "public/test/earl_grey/js_test_util.mm",
+    "public/test/earl_grey/web_view_actions.h",
+    "public/test/earl_grey/web_view_actions.mm",
+    "public/test/earl_grey/web_view_matchers.h",
+    "public/test/earl_grey/web_view_matchers.mm",
+  ]
+}
+
 source_set("run_all_unittests") {
   testonly = true
   sources = [
diff --git a/ios/web/public/test/earl_grey/js_test_util.mm b/ios/web/public/test/earl_grey/js_test_util.mm
index b754f324..50ac21f 100644
--- a/ios/web/public/test/earl_grey/js_test_util.mm
+++ b/ios/web/public/test/earl_grey/js_test_util.mm
@@ -4,11 +4,11 @@
 
 #import "ios/web/public/test/earl_grey/js_test_util.h"
 
-#import <EarlGrey/EarlGrey.h>
 #import <WebKit/WebKit.h>
 
 #import "base/test/ios/wait_util.h"
 #include "base/timer/elapsed_timer.h"
+#import "ios/testing/earl_grey/earl_grey_app.h"
 #import "ios/web/interstitials/web_interstitial_impl.h"
 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
 
diff --git a/ios/web/public/test/earl_grey/web_view_actions.h b/ios/web/public/test/earl_grey/web_view_actions.h
index edeac77..4057c8c 100644
--- a/ios/web/public/test/earl_grey/web_view_actions.h
+++ b/ios/web/public/test/earl_grey/web_view_actions.h
@@ -7,12 +7,11 @@
 
 #include <string>
 
-#import <EarlGrey/EarlGrey.h>
-
-#include "ios/web/public/test/element_selector.h"
-#import "ios/web/public/web_state/web_state.h"
+@class ElementSelector;
+@protocol GREYAction;
 
 namespace web {
+class WebState;
 
 // Action wrapper that performs |action| on the webview of |state|.
 // The action will fail (in addition to its own failure modes) if the element
diff --git a/ios/web/public/test/earl_grey/web_view_actions.mm b/ios/web/public/test/earl_grey/web_view_actions.mm
index 3099e4d..138f905 100644
--- a/ios/web/public/test/earl_grey/web_view_actions.mm
+++ b/ios/web/public/test/earl_grey/web_view_actions.mm
@@ -14,8 +14,11 @@
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #include "base/values.h"
+#import "ios/testing/earl_grey/earl_grey_app.h"
 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
+#include "ios/web/public/test/element_selector.h"
 #import "ios/web/public/test/web_view_interaction_test_util.h"
+#import "ios/web/public/web_state/web_state.h"
 #import "ios/web/web_state/web_state_impl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -198,11 +201,14 @@
     }
 
     // Run the action and wait for the UI to settle.
-    [[EarlGrey selectElementWithMatcher:WebViewInWebState(state)]
+    NSError* actionError = nil;
+    [[[GREYElementInteraction alloc]
+        initWithElementMatcher:WebViewInWebState(state)]
         performAction:action
-                error:error];
+                error:&actionError];
 
-    if (*error) {
+    if (actionError) {
+      *error = actionError;
       return NO;
     }
     [[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
diff --git a/ios/web/public/test/earl_grey/web_view_matchers.mm b/ios/web/public/test/earl_grey/web_view_matchers.mm
index d1c250e..4b721b9 100644
--- a/ios/web/public/test/earl_grey/web_view_matchers.mm
+++ b/ios/web/public/test/earl_grey/web_view_matchers.mm
@@ -4,7 +4,6 @@
 
 #import "ios/web/public/test/earl_grey/web_view_matchers.h"
 
-#import <EarlGrey/EarlGrey.h>
 #import <UIKit/UIKit.h>
 #import <WebKit/WebKit.h>
 
@@ -12,9 +11,10 @@
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#import "ios/testing/earl_grey/earl_grey_app.h"
 #import "ios/web/interstitials/web_interstitial_impl.h"
-#import "ios/web/public/test/earl_grey/js_test_util.h"
 #import "ios/web/public/test/web_view_interaction_test_util.h"
+#import "ios/web/public/web_state/web_state.h"
 #import "net/base/mac/url_conversions.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -43,12 +43,12 @@
 namespace web {
 
 id<GREYMatcher> WebViewInWebState(WebState* web_state) {
-  MatchesBlock matches = ^BOOL(UIView* view) {
+  GREYMatchesBlock matches = ^BOOL(UIView* view) {
     return [view isKindOfClass:[WKWebView class]] &&
            [view isDescendantOfView:web_state->GetView()];
   };
 
-  DescribeToBlock describe = ^(id<GREYDescription> description) {
+  GREYDescribeToBlock describe = ^(id<GREYDescription> description) {
     [description appendText:@"web view in web state"];
   };
 
@@ -57,13 +57,13 @@
 }
 
 id<GREYMatcher> WebViewScrollView(WebState* web_state) {
-  MatchesBlock matches = ^BOOL(UIView* view) {
+  GREYMatchesBlock matches = ^BOOL(UIView* view) {
     return [view isKindOfClass:[UIScrollView class]] &&
            [view.superview isKindOfClass:[WKWebView class]] &&
            [view isDescendantOfView:web_state->GetView()];
   };
 
-  DescribeToBlock describe = ^(id<GREYDescription> description) {
+  GREYDescribeToBlock describe = ^(id<GREYDescription> description) {
     [description appendText:@"web view scroll view"];
   };
 
@@ -72,14 +72,14 @@
 }
 
 id<GREYMatcher> Interstitial(WebState* web_state) {
-  MatchesBlock matches = ^BOOL(WKWebView* view) {
+  GREYMatchesBlock matches = ^BOOL(WKWebView* view) {
     web::WebInterstitialImpl* interstitial =
         static_cast<web::WebInterstitialImpl*>(web_state->GetWebInterstitial());
     return interstitial &&
            [view isDescendantOfView:interstitial->GetContentView()];
   };
 
-  DescribeToBlock describe = ^(id<GREYDescription> description) {
+  GREYDescribeToBlock describe = ^(id<GREYDescription> description) {
     [description appendText:@"interstitial displayed"];
   };
 
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 4f3a9e87..9c136c4 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -5247,6 +5247,13 @@
       return;
     }
 
+    if (error.code == web::kWebKitErrorUrlBlockedByContentFilter &&
+        web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+      // If URL is blocked due to Restriction, do not take any further action as
+      // WKWebView will show a built-in error.
+      return;
+    }
+
     if (error.code == web::kWebKitErrorFrameLoadInterruptedByPolicyChange) {
       // This method should not be called if the navigation was cancelled by
       // embedder.
diff --git a/mojo/core/channel_mac.cc b/mojo/core/channel_mac.cc
index 0ea384d..8820afe 100644
--- a/mojo/core/channel_mac.cc
+++ b/mojo/core/channel_mac.cc
@@ -181,7 +181,10 @@
       DCHECK(receive_port_ == MACH_PORT_NULL);
       CHECK(base::mac::CreateMachPort(&receive_port_, nullptr,
                                       MACH_PORT_QLIMIT_LARGE));
-      RequestSendDeadNameNotification();
+      if (!RequestSendDeadNameNotification()) {
+        OnError(Error::kConnectionFailed);
+        return;
+      }
       SendHandshake();
     } else if (receive_port_ != MACH_PORT_NULL) {
       DCHECK(send_port_ == MACH_PORT_NULL);
@@ -219,15 +222,20 @@
   // Requests that the kernel notify the |receive_port_| when the receive right
   // connected to |send_port_| becomes a dead name. This should be called as
   // soon as the Channel establishes both the send and receive ports.
-  void RequestSendDeadNameNotification() {
+  bool RequestSendDeadNameNotification() {
     base::mac::ScopedMachSendRight previous;
     kern_return_t kr = mach_port_request_notification(
         mach_task_self(), send_port_.get(), MACH_NOTIFY_DEAD_NAME, 0,
         receive_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE,
         base::mac::ScopedMachSendRight::Receiver(previous).get());
     if (kr != KERN_SUCCESS) {
-      MACH_LOG(ERROR, kr) << "mach_port_request_notification";
+      // If port is already a dead name (i.e. the receiver is already gone),
+      // then the channel should be shut down by the caller.
+      MACH_LOG_IF(ERROR, kr != KERN_INVALID_ARGUMENT, kr)
+          << "mach_port_request_notification";
+      return false;
     }
+    return true;
   }
 
   // SendHandshake() sends to the |receive_port_| a right to |send_port_|,
@@ -285,7 +293,10 @@
 
     send_port_ = base::mac::ScopedMachSendRight(message->msgh_remote_port);
 
-    RequestSendDeadNameNotification();
+    if (!RequestSendDeadNameNotification()) {
+      OnError(Error::kConnectionFailed);
+      return false;
+    }
 
     base::AutoLock lock(write_lock_);
     handshake_done_ = true;
diff --git a/mojo/public/js/bindings_lite.js b/mojo/public/js/bindings_lite.js
index c7fd658..e16d8db 100644
--- a/mojo/public/js/bindings_lite.js
+++ b/mojo/public/js/bindings_lite.js
@@ -84,7 +84,7 @@
  */
 mojo.internal.setInt64 = function(dataView, byteOffset, value) {
   if (mojo.internal.kHostLittleEndian) {
-    dataView.setInt32(
+    dataView.setUint32(
         byteOffset, Number(BigInt(value) & BigInt(0xffffffff)),
         mojo.internal.kHostLittleEndian);
     dataView.setInt32(
@@ -94,7 +94,7 @@
     dataView.setInt32(
         byteOffset, Number(BigInt(value) >> BigInt(32)),
         mojo.internal.kHostLittleEndian);
-    dataView.setInt32(
+    dataView.setUint32(
         byteOffset + 4, Number(BigInt(value) & BigInt(0xffffffff)),
         mojo.internal.kHostLittleEndian);
   }
@@ -131,10 +131,10 @@
 mojo.internal.getInt64 = function(dataView, byteOffset) {
   let low, high;
   if (mojo.internal.kHostLittleEndian) {
-    low = dataView.getInt32(byteOffset, mojo.internal.kHostLittleEndian);
+    low = dataView.getUint32(byteOffset, mojo.internal.kHostLittleEndian);
     high = dataView.getInt32(byteOffset + 4, mojo.internal.kHostLittleEndian);
   } else {
-    low = dataView.getInt32(byteOffset + 4, mojo.internal.kHostLittleEndian);
+    low = dataView.getUint32(byteOffset + 4, mojo.internal.kHostLittleEndian);
     high = dataView.getInt32(byteOffset, mojo.internal.kHostLittleEndian);
   }
   const value = (BigInt(high) << BigInt(32)) | BigInt(low);
diff --git a/net/base/network_change_notifier_mac.cc b/net/base/network_change_notifier_mac.cc
index 95f60e99..7f11018 100644
--- a/net/base/network_change_notifier_mac.cc
+++ b/net/base/network_change_notifier_mac.cc
@@ -10,9 +10,9 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
-#include "base/threading/thread.h"
-#include "base/threading/thread_restrictions.h"
-#include "build/build_config.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
 #include "net/dns/dns_config_service.h"
 
 namespace net {
@@ -23,44 +23,36 @@
   return reachable && !connection_required;
 }
 
-// Thread on which we can run DnsConfigService, which requires a TYPE_IO
-// message loop.
-class NetworkChangeNotifierMac::DnsConfigServiceThread : public base::Thread {
- public:
-  DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
-
-  ~DnsConfigServiceThread() override { Stop(); }
-
-  void Init() override {
-    service_ = DnsConfigService::CreateSystemService();
-    service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
-  }
-
-  void CleanUp() override { service_.reset(); }
-
- private:
-  std::unique_ptr<DnsConfigService> service_;
-
-  DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
-};
-
 NetworkChangeNotifierMac::NetworkChangeNotifierMac()
     : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
       connection_type_(CONNECTION_UNKNOWN),
       connection_type_initialized_(false),
       initial_connection_type_cv_(&connection_type_lock_),
-      forwarder_(this) {
-  // Must be initialized after the rest of this object, as it may call back into
-  // SetInitialConnectionType().
-  config_watcher_ = std::make_unique<NetworkConfigWatcherMac>(&forwarder_);
+      forwarder_(this)
 #if !defined(OS_IOS)
+      ,
+      dns_config_service_runner_(
+          base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})),
+      dns_config_service_(
+          DnsConfigService::CreateSystemService().release(),
+          // Ensure DnsConfigService lives on |dns_config_service_runner_|
+          // to prevent races where NetworkChangeNotifierPosix outlives
+          // ScopedTaskEnvironment. https://crbug.com/938126
+          base::OnTaskRunnerDeleter(dns_config_service_runner_)) {
   // DnsConfigService on iOS doesn't watch the config so its result can become
   // inaccurate at any time.  Disable it to prevent promulgation of inaccurate
   // DnsConfigs.
-  dns_config_service_thread_ = std::make_unique<DnsConfigServiceThread>();
-  dns_config_service_thread_->StartWithOptions(
-      base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
-#endif
+  dns_config_service_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&DnsConfigService::WatchConfig,
+                                base::Unretained(dns_config_service_.get()),
+                                base::BindRepeating(
+                                    &NetworkChangeNotifier::SetDnsConfig)));
+#else
+{
+#endif  // defined(OS_IOS)
+  // Must be initialized after the rest of this object, as it may call back into
+  // SetInitialConnectionType().
+  config_watcher_ = std::make_unique<NetworkConfigWatcherMac>(&forwarder_);
 }
 
 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
diff --git a/net/base/network_change_notifier_mac.h b/net/base/network_change_notifier_mac.h
index 34b1266..4037534 100644
--- a/net/base/network_change_notifier_mac.h
+++ b/net/base/network_change_notifier_mac.h
@@ -12,13 +12,22 @@
 #include "base/compiler_specific.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/synchronization/condition_variable.h"
 #include "base/synchronization/lock.h"
+#include "build/build_config.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_config_watcher_mac.h"
 
+namespace base {
+class SequencedTaskRunner;
+struct OnTaskRunnerDeleter;
+}  // namespace base
+
 namespace net {
 
+class DnsConfigService;
+
 class NetworkChangeNotifierMac: public NetworkChangeNotifier {
  public:
   NetworkChangeNotifierMac();
@@ -46,8 +55,6 @@
   };
 
  private:
-  class DnsConfigServiceThread;
-
   // Called on the main thread on startup, afterwards on the notifier thread.
   static ConnectionType CalculateConnectionType(SCNetworkConnectionFlags flags);
 
@@ -76,7 +83,13 @@
   Forwarder forwarder_;
   std::unique_ptr<const NetworkConfigWatcherMac> config_watcher_;
 
-  std::unique_ptr<DnsConfigServiceThread> dns_config_service_thread_;
+#if !defined(OS_IOS)
+  // |dns_config_service_| will live on this runner.
+  scoped_refptr<base::SequencedTaskRunner> dns_config_service_runner_;
+  // DnsConfigService that lives on |dns_config_service_runner_|.
+  std::unique_ptr<DnsConfigService, base::OnTaskRunnerDeleter>
+      dns_config_service_;
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierMac);
 };
diff --git a/services/resource_coordinator/public/mojom/BUILD.gn b/services/resource_coordinator/public/mojom/BUILD.gn
index 932c3d6..79769fd1 100644
--- a/services/resource_coordinator/public/mojom/BUILD.gn
+++ b/services/resource_coordinator/public/mojom/BUILD.gn
@@ -15,7 +15,6 @@
     "memory_instrumentation/memory_instrumentation.mojom",
     "page_signal.mojom",
     "service_constants.mojom",
-    "signals.mojom",
     "webui_graph_dump.mojom",
   ]
 
diff --git a/services/resource_coordinator/public/mojom/coordination_unit.mojom b/services/resource_coordinator/public/mojom/coordination_unit.mojom
index a594ac41..99e2196 100644
--- a/services/resource_coordinator/public/mojom/coordination_unit.mojom
+++ b/services/resource_coordinator/public/mojom/coordination_unit.mojom
@@ -7,7 +7,6 @@
 import "mojo/public/mojom/base/process_id.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/resource_coordinator/public/mojom/lifecycle.mojom";
-import "services/resource_coordinator/public/mojom/signals.mojom";
 
 // Any new type here needs to be mirrored between coordination_unit_types.h and
 // coordination_unit.mojom, and have mappings between the two defined in
diff --git a/services/resource_coordinator/public/mojom/signals.mojom b/services/resource_coordinator/public/mojom/signals.mojom
deleted file mode 100644
index 94c83f78..0000000
--- a/services/resource_coordinator/public/mojom/signals.mojom
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module resource_coordinator.mojom;
-
-enum Event {
-  kTestEvent,
-  kFaviconUpdated,
-  // This event signal is received when main frame navigation is committed.
-  kNavigationCommitted,
-  // Only care about non-persistent notifications, notifications launched from
-  // ServiceWorker are persistent and compatible with LifeCycle.
-  kNonPersistentNotificationCreated,
-  kTitleUpdated,
-  // This signal is sent to a SystemCU when all ProcessCU CPU usage estimates
-  // have been updated and are coherent.
-  kProcessCPUUsageReady,
-  // This signal is set to the renderer ProcessCU.
-  kRendererIsBloated,
-};
diff --git a/services/ws/client_root.cc b/services/ws/client_root.cc
index a47fa2e..dba4cde 100644
--- a/services/ws/client_root.cc
+++ b/services/ws/client_root.cc
@@ -18,6 +18,7 @@
 #include "services/ws/top_level_proxy_window.h"
 #include "services/ws/window_service.h"
 #include "services/ws/window_tree.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
 #include "ui/aura/mus/client_surface_embedder.h"
 #include "ui/aura/mus/property_converter.h"
@@ -344,6 +345,7 @@
                          TRACE_EVENT_FLAG_FLOW_OUT);
   window_tree_->window_tree_client_->OnWindowBoundsChanged(
       window_tree_->TransportIdForWindow(window_), last_bounds_,
+      window_->GetProperty(aura::client::kShowStateKey),
       ProxyWindow::GetMayBeNull(window_)->local_surface_id_allocation());
 }
 
diff --git a/services/ws/public/mojom/window_tree.mojom b/services/ws/public/mojom/window_tree.mojom
index 70ba315..aca3358 100644
--- a/services/ws/public/mojom/window_tree.mojom
+++ b/services/ws/public/mojom/window_tree.mojom
@@ -519,11 +519,14 @@
       bool parent_drawn,
       viz.mojom.LocalSurfaceIdAllocation local_surface_id_allocation);
 
-  // Invoked when a window's bounds have changed. |local_surface_id_allocation|
-  // is only supplied for roots.
+  // Invoked when a window's bounds have changed. |state| is supplied for roots,
+  // and SHOW_STATE_DEFAULT for non-roots. It allows the client to
+  // simultaneously update both bounds and show state, e.g. after a maximize
+  // operation. |local_surface_id_allocation| is only supplied for roots.
   OnWindowBoundsChanged(
       uint64 window,
       gfx.mojom.Rect new_bounds,
+      ui.mojom.WindowShowState state,
       viz.mojom.LocalSurfaceIdAllocation? local_surface_id_allocation);
 
   OnWindowTransformChanged(uint64 window,
diff --git a/services/ws/public/mojom/window_tree_constants.mojom b/services/ws/public/mojom/window_tree_constants.mojom
index cc69965..edf9d3d 100644
--- a/services/ws/public/mojom/window_tree_constants.mojom
+++ b/services/ws/public/mojom/window_tree_constants.mojom
@@ -4,6 +4,7 @@
 
 module ws.mojom;
 
+import "ui/base/mojo/ui_base_types.mojom";
 import "ui/display/mojo/display.mojom";
 import "ui/gfx/geometry/mojo/geometry.mojom";
 
@@ -30,6 +31,8 @@
 
   gfx.mojom.Rect bounds;
 
+  ui.mojom.WindowShowState state = ui.mojom.WindowShowState.kDefault;
+
   // Arbitrary key/value pairs. The interpretation of these is left to the
   // client. See SetWindowProperty() for more information.
   map<string, array<uint8>> properties;
diff --git a/services/ws/test_change_tracker.cc b/services/ws/test_change_tracker.cc
index 74b7b9b..3f8e056 100644
--- a/services/ws/test_change_tracker.cc
+++ b/services/ws/test_change_tracker.cc
@@ -356,12 +356,14 @@
 void TestChangeTracker::OnWindowBoundsChanged(
     Id window_id,
     const gfx::Rect& new_bounds,
+    ui::WindowShowState new_state,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         local_surface_id_allocation) {
   Change change;
   change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED;
   change.window_id = window_id;
   change.bounds = new_bounds;
+  change.state = new_state;
   change.local_surface_id_allocation = local_surface_id_allocation;
   AddChange(change);
 }
diff --git a/services/ws/test_change_tracker.h b/services/ws/test_change_tracker.h
index 6a512b8..cf6be0e 100644
--- a/services/ws/test_change_tracker.h
+++ b/services/ws/test_change_tracker.h
@@ -90,6 +90,7 @@
   Id window_id2 = 0;
   Id window_id3 = 0;
   gfx::Rect bounds;
+  ui::WindowShowState state;
   viz::FrameSinkId frame_sink_id;
   base::Optional<viz::LocalSurfaceIdAllocation> local_surface_id_allocation;
   int32_t event_action = 0;
@@ -190,6 +191,7 @@
   void OnWindowBoundsChanged(
       Id window_id,
       const gfx::Rect& new_bounds,
+      ui::WindowShowState state,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation);
   void OnWindowTransformChanged(Id window_id);
diff --git a/services/ws/test_window_tree_client.cc b/services/ws/test_window_tree_client.cc
index 06caae4f..838300b 100644
--- a/services/ws/test_window_tree_client.cc
+++ b/services/ws/test_window_tree_client.cc
@@ -124,6 +124,7 @@
 void TestWindowTreeClient::OnWindowBoundsChanged(
     Id window_id,
     const gfx::Rect& new_bounds,
+    ui::WindowShowState state,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         local_surface_id_allocation) {
   // The bounds of the root may change during startup on Android at random
@@ -131,7 +132,7 @@
   // it is ignored.
   if (window_id == root_window_id_ && !track_root_bounds_changes_)
     return;
-  tracker_.OnWindowBoundsChanged(window_id, new_bounds,
+  tracker_.OnWindowBoundsChanged(window_id, new_bounds, state,
                                  local_surface_id_allocation);
 }
 
diff --git a/services/ws/test_window_tree_client.h b/services/ws/test_window_tree_client.h
index 9065d74..3707a11 100644
--- a/services/ws/test_window_tree_client.h
+++ b/services/ws/test_window_tree_client.h
@@ -111,6 +111,7 @@
   void OnWindowBoundsChanged(
       Id window_id,
       const gfx::Rect& new_bounds,
+      ui::WindowShowState state,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation) override;
   void OnWindowTransformChanged(Id window_id,
diff --git a/services/ws/window_tree.cc b/services/ws/window_tree.cc
index d67fe66..0f8cbfd 100644
--- a/services/ws/window_tree.cc
+++ b/services/ws/window_tree.cc
@@ -705,6 +705,9 @@
                        : kInvalidTransportId;
   window_data->bounds =
       is_top_level ? window->GetBoundsInScreen() : window->bounds();
+  window_data->state = is_top_level
+                           ? window->GetProperty(aura::client::kShowStateKey)
+                           : ui::SHOW_STATE_DEFAULT;
   window_data->properties =
       window_service_->property_converter()->GetTransportProperties(window);
   window_data->visible = (!IsClientRootWindow(window) || is_top_level)
@@ -1298,9 +1301,10 @@
   // The window's bounds changed, but not to the value the client requested.
   // Tell the client the new value, and return false, which triggers the client
   // to use the value supplied to OnWindowBoundsChanged().
-  window_tree_client_->OnWindowBoundsChanged(TransportIdForWindow(window),
-                                             window->bounds(),
-                                             local_surface_id_allocation);
+  window_tree_client_->OnWindowBoundsChanged(
+      TransportIdForWindow(window), window->bounds(),
+      window->GetProperty(aura::client::kShowStateKey),
+      local_surface_id_allocation);
   return false;
 }
 
diff --git a/services/ws/window_tree_client_unittest.cc b/services/ws/window_tree_client_unittest.cc
index f26bb68..3b4f250 100644
--- a/services/ws/window_tree_client_unittest.cc
+++ b/services/ws/window_tree_client_unittest.cc
@@ -325,6 +325,7 @@
   void OnWindowBoundsChanged(
       Id window_id,
       const gfx::Rect& new_bounds,
+      ui::WindowShowState state,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation) override {
     // The bounds of the root may change during startup on Android at random
@@ -332,7 +333,7 @@
     // it is ignored.
     if (window_id == root_window_id_ && !track_root_bounds_changes_)
       return;
-    tracker()->OnWindowBoundsChanged(window_id, new_bounds,
+    tracker()->OnWindowBoundsChanged(window_id, new_bounds, state,
                                      local_surface_id_allocation);
   }
   void OnWindowTransformChanged(Id window_id,
diff --git a/skia/ext/SkOpts_hsw_stub.cc b/skia/ext/SkOpts_hsw_stub.cc
deleted file mode 100644
index 70573c85..0000000
--- a/skia/ext/SkOpts_hsw_stub.cc
+++ /dev/null
@@ -1,13 +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.
-
-// This is SkOpts_hsw.cpp, stubbed out to do nothing. This is used by the
-// SyzyAsan builds because the Syzygy instrumentation pipeline doesn't support
-// the AVX2 and F16C instructions.
-
-namespace SkOpts {
-    void Init_hsw();
-    void Init_hsw() {}
-}
-
diff --git a/storage/browser/blob/README.md b/storage/browser/blob/README.md
index 9ed3fa8..3543ed45 100644
--- a/storage/browser/blob/README.md
+++ b/storage/browser/blob/README.md
@@ -311,25 +311,22 @@
 responsible for creating & managing all the state of constructing blobs, as
 well as all blob handle generation and general blob status access.
 
-When a `BlobDataBuilder` is given to the context, whether from the
-`BlobTransportHost` or from elsewhere, the context will do the following:
+When a `BlobDataBuilder` is given to the context, it will do the following:
 
 1. Find all dependent blobs in the new blob (any blob reference in the blob
 item list), and create a 'slice' of their items for the new blob.
 2. Create the final blob item list representation, which creates a new blob
 item list which inserts these 'slice' items into the blob reference spots. This
 is 'flattening' the blob.
-3. Ask the `BlobMemoryManager` for file or memory quota for the transportation
-if necessary
-  * When the quota request is granted, notify the `BlobTransportHost` that to
-  begin transporting the data.
-4. Ask the `BlobMemoryManager` for memory quota for any copies necessary for
+3. Ask the `BlobMemoryController` for file or memory quota for the
+transportation if necessary.
+4. Ask the `BlobMemoryController` for memory quota for any copies necessary for
 blob slicing.
 5. Adds completion callbacks to any blobs our blob depends on.
 
 When all of the following conditions are met:
 
-1. The `BlobTransportHost` tells us it has transported all the data (or we
+1. The `BlobRegistry` tells us it has transported all the data (or we
 don't need to transport data),
 2. The `BlobMemoryManager` approves our memory quota for slice copies (or we
 don't need slice copies), and
diff --git a/storage/browser/blob/blob_storage_context.h b/storage/browser/blob/blob_storage_context.h
index a323049..25b32c6 100644
--- a/storage/browser/blob/blob_storage_context.h
+++ b/storage/browser/blob/blob_storage_context.h
@@ -32,7 +32,6 @@
 namespace content {
 class BlobDispatcherHost;
 class BlobDispatcherHostTest;
-class BlobTransportHostTest;
 class ChromeBlobStorageContext;
 class ShareableBlobDataItem;
 }
@@ -164,10 +163,8 @@
  protected:
   friend class content::BlobDispatcherHost;
   friend class content::BlobDispatcherHostTest;
-  friend class content::BlobTransportHostTest;
   friend class content::ChromeBlobStorageContext;
   friend class BlobBuilderFromStream;
-  friend class BlobTransportHost;
   friend class BlobDataHandle;
   friend class BlobDataHandle::BlobDataHandleShared;
   friend class BlobRegistryImplTest;
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 957ee9ce..fefc291f 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2263,6 +2263,8 @@
   kInputTypeReset = 2855,
   kSelectElementSingle = 2856,
   kSelectElementMultiple = 2857,
+  kV8Animation_Effect_AttributeGetter = 2858,
+  kV8Animation_Effect_AttributeSetter = 2859,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_crypto.h b/third_party/blink/public/platform/web_crypto.h
index f2f2dd4..570ebc77 100644
--- a/third_party/blink/public/platform/web_crypto.h
+++ b/third_party/blink/public/platform/web_crypto.h
@@ -109,31 +109,6 @@
       cancel_;
 };
 
-class WebCryptoDigestor {
- public:
-  virtual ~WebCryptoDigestor() = default;
-
-  // Consume() will return |true| on the successful addition of data to the
-  // partially generated digest. It will return |false| when that fails. After
-  // a return of |false|, Consume() should not be called again (nor should
-  // Finish() be called).
-  virtual bool Consume(const unsigned char* data, unsigned data_size) {
-    return false;
-  }
-
-  // Finish() will return |true| if the digest has been successfully computed
-  // and put into the result buffer, otherwise it will return |false|. In
-  // either case, neither Finish() nor Consume() should be called again after
-  // a call to Finish(). |result_data| is valid until the WebCrytpoDigestor
-  // object is destroyed.
-  virtual bool Finish(unsigned char*& result_data, unsigned& result_data_size) {
-    return false;
-  }
-
- protected:
-  WebCryptoDigestor() = default;
-};
-
 class WebCrypto {
  public:
   // WebCrypto is the interface for starting one-shot cryptographic
@@ -308,15 +283,6 @@
     result.CompleteWithError(kWebCryptoErrorTypeNotSupported, "");
   }
 
-  // This is the exception to the "Completing the request" guarantees
-  // outlined above. This is useful for Blink internal crypto and is not part
-  // of the WebCrypto standard. CreateDigestor must provide the result via
-  // the WebCryptoDigestor object synchronously. This will never return null.
-  virtual std::unique_ptr<WebCryptoDigestor> CreateDigestor(
-      WebCryptoAlgorithmId algorithm_id) {
-    return nullptr;
-  }
-
   // -----------------------
   // Structured clone
   // -----------------------
diff --git a/third_party/blink/public/web/web_ax_object.h b/third_party/blink/public/web/web_ax_object.h
index f56b763e..4d227af5 100644
--- a/third_party/blink/public/web/web_ax_object.h
+++ b/third_party/blink/public/web/web_ax_object.h
@@ -186,8 +186,11 @@
   BLINK_EXPORT WebString StringValue() const;
   BLINK_EXPORT ax::mojom::TextDirection GetTextDirection() const;
   BLINK_EXPORT ax::mojom::TextPosition GetTextPosition() const;
-  // Bitmask from ax::mojom::TextStyle.
-  BLINK_EXPORT int32_t TextStyle() const;
+  BLINK_EXPORT void GetTextStyleAndTextDecorationStyle(
+      int32_t* text_style,
+      ax::mojom::TextDecorationStyle* text_overline_style,
+      ax::mojom::TextDecorationStyle* text_strikethrough_style,
+      ax::mojom::TextDecorationStyle* text_underline_style) const;
   BLINK_EXPORT WebURL Url() const;
 
   // Retrieves the accessible name of the object, an enum indicating where the
diff --git a/third_party/blink/renderer/core/animation/animation.idl b/third_party/blink/renderer/core/animation/animation.idl
index 1c5fb3f..7b78ea2 100644
--- a/third_party/blink/renderer/core/animation/animation.idl
+++ b/third_party/blink/renderer/core/animation/animation.idl
@@ -28,7 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-// https://drafts.csswg.org/web-animations/#animation
+// https://drafts.csswg.org/web-animations/#the-animation-interface
 
 enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
 
@@ -39,8 +39,8 @@
     RaisesException=Constructor,
     ActiveScriptWrappable
 ] interface Animation : EventTarget {
+    [Measure] attribute AnimationEffect? effect;
     // TODO(suzyh): Make timeline mutable.
-    [RuntimeEnabled=WebAnimationsAPI] attribute AnimationEffect? effect;
     [RuntimeEnabled=WebAnimationsAPI] readonly attribute AnimationTimeline? timeline;
     [Measure] attribute double?    startTime;
     [Measure, RaisesException=Setter] attribute double?    currentTime;
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index 5750b86..27597d21 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -208,7 +208,9 @@
           current_phase, timing_);
 
       current_iteration = CalculateCurrentIteration(
-          iteration_duration, iteration_time, offset_active_time, timing_);
+          current_phase, active_time, iteration_duration,
+          timing_.iteration_count, timing_.iteration_start);
+
       const base::Optional<double> transformed_time = CalculateTransformedTime(
           current_iteration, iteration_duration, iteration_time, timing_);
 
@@ -259,8 +261,11 @@
           kLocalIterationDuration, local_active_duration, offset_active_time,
           start_offset, current_phase, timing_);
 
-      current_iteration = CalculateCurrentIteration(
-          kLocalIterationDuration, iteration_time, offset_active_time, timing_);
+      current_iteration = CalculateCurrentIteration(current_phase, active_time,
+                                                    /*iteration_duration=*/0,
+                                                    timing_.iteration_count,
+                                                    timing_.iteration_start);
+
       progress = CalculateTransformedTime(
           current_iteration, kLocalIterationDuration, iteration_time, timing_);
     }
diff --git a/third_party/blink/renderer/core/animation/animation_effect.idl b/third_party/blink/renderer/core/animation/animation_effect.idl
index 1aca06b..bf779a5 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.idl
+++ b/third_party/blink/renderer/core/animation/animation_effect.idl
@@ -30,9 +30,8 @@
 
 // https://drafts.csswg.org/web-animations/#the-animationeffect-interface
 
-[
-    RuntimeEnabled=WebAnimationsAPI
-] interface AnimationEffect {
+[Exposed=Window]
+interface AnimationEffect {
     EffectTiming getTiming();
     ComputedEffectTiming getComputedTiming();
     [RaisesException] void updateTiming(optional OptionalEffectTiming timing);
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index f89d2a6..be54de7 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -63,7 +63,6 @@
     const ScriptValue& keyframes,
     const UnrestrictedDoubleOrKeyframeEffectOptions& options,
     ExceptionState& exception_state) {
-  DCHECK(RuntimeEnabledFeatures::WebAnimationsAPIEnabled());
   if (element) {
     UseCounter::Count(
         element->GetDocument(),
@@ -92,7 +91,6 @@
                                        Element* element,
                                        const ScriptValue& keyframes,
                                        ExceptionState& exception_state) {
-  DCHECK(RuntimeEnabledFeatures::WebAnimationsAPIEnabled());
   if (element) {
     UseCounter::Count(
         element->GetDocument(),
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.idl b/third_party/blink/renderer/core/animation/keyframe_effect.idl
index 79a9096..c1e196c 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.idl
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.idl
@@ -28,19 +28,19 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-// https://drafts.csswg.org/web-animations/#the-keyframeeffect-interfaces
+// https://drafts.csswg.org/web-animations/#the-keyframeeffect-interface
 
 enum CompositeOperation { "replace", "add", "accumulate" };
 
 [
+    Exposed=Window,
     Constructor(Element? target, object? keyframes, optional (unrestricted double or KeyframeEffectOptions) options),
     Constructor(KeyframeEffect source),
     ConstructorCallWith=ScriptState,
-    RaisesException=Constructor,
-    RuntimeEnabled=WebAnimationsAPI
+    RaisesException=Constructor
 ] interface KeyframeEffect : AnimationEffect {
   attribute Element? target;
-  attribute CompositeOperation composite;
-  [CallWith=ScriptState] sequence<object> getKeyframes();
-  [CallWith=ScriptState, RaisesException] void setKeyframes(object? keyframes);
+  [RuntimeEnabled=WebAnimationsAPI] attribute CompositeOperation composite;
+  [CallWith=ScriptState, RuntimeEnabled=WebAnimationsAPI] sequence<object> getKeyframes();
+  [CallWith=ScriptState, RaisesException, RuntimeEnabled=WebAnimationsAPI] void setKeyframes(object? keyframes);
 };
diff --git a/third_party/blink/renderer/core/animation/timing_calculations.h b/third_party/blink/renderer/core/animation/timing_calculations.h
index 0002986..37f28a20 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations.h
+++ b/third_party/blink/renderer/core/animation/timing_calculations.h
@@ -50,6 +50,12 @@
   return x.is_zero() || y == 0 ? 0 : (x * y).InSecondsF();
 }
 
+static inline bool IsWithinEpsilon(double a, double b) {
+  // Permit 2-bits of quantization error. Threshold based on experimentation
+  // with accuracy of fmod.
+  return std::abs(a - b) <= 2.0 * std::numeric_limits<double>::epsilon();
+}
+
 // https://drafts.csswg.org/web-animations-1/#animation-effect-phases-and-states
 static inline AnimationEffect::Phase CalculatePhase(
     double active_duration,
@@ -170,27 +176,104 @@
   return iteration_time;
 }
 
-static inline double CalculateCurrentIteration(double iteration_duration,
-                                               double iteration_time,
-                                               double offset_active_time,
-                                               const Timing& specified) {
-  DCHECK_GT(iteration_duration, 0);
-  DCHECK(IsNull(iteration_time) || iteration_time >= 0);
-
-  if (IsNull(offset_active_time))
+// Calculates the overall progress, which describes the number of iterations
+// that have completed (including partial iterations).
+// https://drafts.csswg.org/web-animations/#calculating-the-overall-progress
+static inline double CalculateOverallProgress(AnimationEffect::Phase phase,
+                                              double active_time,
+                                              double iteration_duration,
+                                              double iteration_count,
+                                              double iteration_start) {
+  // 1. If the active time is unresolved, return unresolved.
+  if (IsNull(active_time))
     return NullValue();
 
-  DCHECK_GE(iteration_time, 0);
-  DCHECK_LE(iteration_time, iteration_duration);
-  DCHECK_GE(offset_active_time, 0);
+  // 2. Calculate an initial value for overall progress.
+  double overall_progress = 0;
+  if (!iteration_duration) {
+    if (phase != AnimationEffect::kPhaseBefore)
+      overall_progress = iteration_count;
+  } else {
+    overall_progress = active_time / iteration_duration;
+  }
 
-  if (!offset_active_time)
-    return 0;
+  return overall_progress + iteration_start;
+}
 
-  if (iteration_time == iteration_duration)
-    return specified.iteration_start + specified.iteration_count - 1;
+// Calculates the simple iteration progress, which is a fraction of the progress
+// through the current iteration that ignores transformations to the time
+// introduced by the playback direction or timing functions applied to the
+// effect.
+// https://drafts.csswg.org/web-animations/#calculating-the-simple-iteration
+// -progress
+static inline double CalculateSimpleIterationProgress(
+    AnimationEffect::Phase phase,
+    double overall_progress,
+    double iteration_start,
+    double active_time,
+    double iteration_duration,
+    double iteration_count) {
+  // 1. If the overall progress is unresolved, return unresolved.
+  if (IsNull(overall_progress))
+    return NullValue();
 
-  return floor(offset_active_time / iteration_duration);
+  // 2. If overall progress is infinity, let the simple iteration progress be
+  // iteration start % 1.0, otherwise, let the simple iteration progress be
+  // overall progress % 1.0.
+  double simple_iteration_progress = std::isinf(overall_progress)
+                                         ? fmod(iteration_start, 1.0)
+                                         : fmod(overall_progress, 1.0);
+
+  const double active_duration = iteration_duration * iteration_count;
+
+  // 3. If all of the following conditions are true,
+  //   * the simple iteration progress calculated above is zero, and
+  //   * the animation effect is in the active phase or the after phase, and
+  //   * the active time is equal to the active duration, and
+  //   * the iteration count is not equal to zero.
+  // let the simple iteration progress be 1.0.
+  if (IsWithinEpsilon(simple_iteration_progress, 0.0) &&
+      (phase == AnimationEffect::kPhaseActive ||
+       phase == AnimationEffect::kPhaseAfter) &&
+      IsWithinEpsilon(active_time, active_duration) &&
+      !IsWithinEpsilon(iteration_count, 0.0)) {
+    simple_iteration_progress = 1.0;
+  }
+
+  // 4. Return simple iteration progress.
+  return simple_iteration_progress;
+}
+
+// https://drafts.csswg.org/web-animations/#calculating-the-current-iteration
+static inline double CalculateCurrentIteration(AnimationEffect::Phase phase,
+                                               double active_time,
+                                               double iteration_duration,
+                                               double iteration_count,
+                                               double iteration_start) {
+  // 1. If the active time is unresolved, return unresolved.
+  if (IsNull(active_time))
+    return NullValue();
+
+  // 2. If the animation effect is in the after phase and the iteration count
+  // is infinity, return infinity.
+  if (phase == AnimationEffect::kPhaseAfter && std::isinf(iteration_count)) {
+    return std::numeric_limits<double>::infinity();
+  }
+
+  const double overall_progress = CalculateOverallProgress(
+      phase, active_time, iteration_duration, iteration_count, iteration_start);
+
+  // 3. If the simple iteration progress is 1.0, return floor(overall progress)
+  // - 1.
+  const double simple_iteration_progress = CalculateSimpleIterationProgress(
+      phase, overall_progress, iteration_start, active_time, iteration_duration,
+      iteration_count);
+
+  if (simple_iteration_progress == 1.0)
+    return floor(overall_progress) - 1;
+
+  // 4. Otherwise, return floor(overall progress).
+  return floor(overall_progress);
 }
 
 static inline double CalculateDirectedTime(double current_iteration,
@@ -203,6 +286,9 @@
   if (IsNull(iteration_time))
     return NullValue();
 
+  if (IsNull(current_iteration))
+    return NullValue();
+
   DCHECK_GE(current_iteration, 0);
   DCHECK_GE(iteration_time, 0);
   DCHECK_LE(iteration_time, iteration_duration);
diff --git a/third_party/blink/renderer/core/animation/timing_calculations_test.cc b/third_party/blink/renderer/core/animation/timing_calculations_test.cc
index 20058c4..7832d32 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations_test.cc
+++ b/third_party/blink/renderer/core/animation/timing_calculations_test.cc
@@ -120,24 +120,55 @@
 }
 
 TEST(AnimationTimingCalculationsTest, CurrentIteration) {
-  Timing timing;
+  // If the active time is null.
+  EXPECT_TRUE(IsNull(CalculateCurrentIteration(AnimationEffect::kPhaseAfter,
+                                               /*active_time=*/NullValue(),
+                                               /*iteration_duration=*/1.0,
+                                               /*iteration_count=*/1.0,
+                                               /*iteration_start=*/1.0)));
 
-  // calculateCurrentIteration(
-  //     iterationDuration, iterationTime, scaledActiveTime, timing)
+  // If the active time is zero.
+  EXPECT_EQ(0, CalculateCurrentIteration(AnimationEffect::kPhaseAfter,
+                                         /*active_time=*/0.0,
+                                         /*iteration_duration=*/1.0,
+                                         /*iteration_count=*/0.0,
+                                         /*iteration_start=*/0.0));
 
-  // if the scaled active time is null
-  EXPECT_TRUE(IsNull(CalculateCurrentIteration(1, 1, NullValue(), timing)));
+  // If the iteration count is infinite.
+  const double inf = std::numeric_limits<double>::infinity();
+  EXPECT_EQ(inf, CalculateCurrentIteration(AnimationEffect::kPhaseAfter,
+                                           /*active_time=*/1.0,
+                                           /*iteration_duration=*/1.0,
+                                           /*iteration_count=*/inf,
+                                           /*iteration_start=*/1.0));
 
-  // if the scaled active time is zero
-  EXPECT_EQ(0, CalculateCurrentIteration(1, 1, 0, timing));
+  // If iteration duration is zero, calculate progress based on iteration count.
+  EXPECT_EQ(3, CalculateCurrentIteration(AnimationEffect::kPhaseActive,
+                                         /*active_time=*/3.0,
+                                         /*iteration_duration=*/0.0,
+                                         /*iteration_count=*/3.0,
+                                         /*iteration_start=*/0.0));
+  // ...unless in before phase, in which case progress is zero.
+  EXPECT_EQ(0, CalculateCurrentIteration(AnimationEffect::kPhaseBefore,
+                                         /*active_time=*/3.0,
+                                         /*iteration_duration=*/0.0,
+                                         /*iteration_count=*/3.0,
+                                         /*iteration_start=*/0.0));
 
-  // if the iteration time equals the iteration duration
-  timing.iteration_start = 4;
-  timing.iteration_count = 7;
-  EXPECT_EQ(10, CalculateCurrentIteration(5, 5, 9, timing));
+  // Hold the endpoint of the final iteration of ending precisely on an
+  // iteration boundary.
+  EXPECT_EQ(2, CalculateCurrentIteration(AnimationEffect::kPhaseAfter,
+                                         /*active_time=*/3.0,
+                                         /*iteration_duration=*/1.0,
+                                         /*iteration_count=*/3.0,
+                                         /*iteration_start=*/0.0));
 
-  // otherwise
-  EXPECT_EQ(3, CalculateCurrentIteration(3.2, 3.1, 10, timing));
+  // Otherwise.
+  EXPECT_EQ(2, CalculateCurrentIteration(AnimationEffect::kPhaseAfter,
+                                         /*active_time=*/2.5,
+                                         /*iteration_duration=*/1.0,
+                                         /*iteration_count=*/0.0,
+                                         /*iteration_start=*/0.0));
 }
 
 TEST(AnimationTimingCalculationsTest, DirectedTime) {
diff --git a/third_party/blink/renderer/core/css/css_style_sheet.cc b/third_party/blink/renderer/core/css/css_style_sheet.cc
index e011bff..77be14b 100644
--- a/third_party/blink/renderer/core/css/css_style_sheet.cc
+++ b/third_party/blink/renderer/core/css/css_style_sheet.cc
@@ -413,11 +413,16 @@
          child_rule_cssom_wrappers_.size() == contents_->RuleCount());
 
   if (index >= length()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kIndexSizeError,
-        "The index provided (" + String::Number(index) +
-            ") is larger than the maximum index (" +
-            String::Number(length() - 1) + ").");
+    if (length()) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kIndexSizeError,
+          "The index provided (" + String::Number(index) +
+              ") is larger than the maximum index (" +
+              String::Number(length() - 1) + ").");
+    } else {
+      exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
+                                        "Style sheet is empty (length 0).");
+    }
     return;
   }
   RuleMutationScope mutation_scope(this);
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 45788ecf..52d04f4f 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -11076,6 +11076,18 @@
                 ->Url());
 }
 
+TEST_F(WebFrameTest, EmptyJavascriptFrameUrl) {
+  std::string url = "data:text/html,<iframe src=\"javascript:''\"></iframe>";
+  frame_test_helpers::WebViewHelper helper;
+  helper.InitializeAndLoad(url);
+  RunPendingTasks();
+
+  LocalFrame* child = To<LocalFrame>(
+      helper.GetWebView()->GetPage()->MainFrame()->Tree().FirstChild());
+  EXPECT_EQ(BlankURL(), child->GetDocument()->Url());
+  EXPECT_EQ(BlankURL(), child->Loader().GetDocumentLoader()->Url());
+}
+
 class TestResourcePriorityWebFrameClient
     : public frame_test_helpers::TestWebFrameClient {
  public:
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 8897879..d3ba936d 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1528,9 +1528,8 @@
 }
 
 void WebViewImpl::BeginUpdateLayers() {
-  if (MainFrameImpl()) {
+  if (MainFrameImpl())
     update_layers_start_time_.emplace(CurrentTimeTicks());
-  }
 }
 
 void WebViewImpl::EndUpdateLayers() {
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 68df7b3..3bb72b4d 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -3542,6 +3542,70 @@
   web_view_helper.Reset();  // Remove dependency on locally scoped client.
 }
 
+class ViewReusingWebViewClient : public frame_test_helpers::TestWebViewClient {
+ public:
+  ViewReusingWebViewClient() = default;
+
+  // WebViewClient methods
+  WebView* CreateView(WebLocalFrame*,
+                      const WebURLRequest&,
+                      const WebWindowFeatures&,
+                      const WebString& name,
+                      WebNavigationPolicy,
+                      bool,
+                      WebSandboxFlags,
+                      const FeaturePolicy::FeatureState&,
+                      const SessionStorageNamespaceId&) override {
+    return web_view_;
+  }
+
+  void SetWebView(WebView* view) { web_view_ = view; }
+
+ private:
+  WebView* web_view_ = nullptr;
+};
+
+class NavigationPolicyWebFrameClient
+    : public frame_test_helpers::TestWebFrameClient {
+ public:
+  NavigationPolicyWebFrameClient() = default;
+
+  void BeginNavigation(std::unique_ptr<WebNavigationInfo> info) override {
+    begin_navigation_called_ = true;
+    last_navigation_policy_ = info->navigation_policy;
+  }
+
+  bool BeginNavigationWasCalled() const { return begin_navigation_called_; }
+  WebNavigationPolicy LastNavigationPolicy() const {
+    return last_navigation_policy_;
+  }
+
+ private:
+  WebNavigationPolicy last_navigation_policy_ = kWebNavigationPolicyCurrentTab;
+  bool begin_navigation_called_ = false;
+};
+
+TEST_F(WebViewTest,
+       ReuseExistingWindowOnCreateViewUsesCorrectNavigationPolicy) {
+  ViewReusingWebViewClient view_client;
+  NavigationPolicyWebFrameClient frame_client;
+  frame_test_helpers::WebViewHelper web_view_helper;
+  WebViewImpl* web_view_impl =
+      web_view_helper.Initialize(&frame_client, &view_client);
+  view_client.SetWebView(web_view_impl);
+  LocalFrame* frame = To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
+
+  // Request a new window, but the WebViewClient will decline to and instead
+  // return the current window.
+  WebURLRequest web_url_request(KURL("about:blank"));
+  FrameLoadRequest request(frame->GetDocument(),
+                           web_url_request.ToResourceRequest(), "_blank");
+  frame->Loader().StartNavigation(request);
+  ASSERT_TRUE(frame_client.BeginNavigationWasCalled());
+  EXPECT_EQ(kWebNavigationPolicyCurrentTab,
+            frame_client.LastNavigationPolicy());
+}
+
 TEST_F(WebViewTest, DispatchesFocusOutFocusInOnViewToggleFocus) {
   RegisterMockedHttpURLLoad("focusout_focusin_events.html");
   WebViewImpl* web_view = web_view_helper_.InitializeAndLoad(
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index b661fab5..5fc087b 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -1477,6 +1477,35 @@
 
   WebWindowFeatures window_features = GetWindowFeaturesFromString(features);
 
+  FrameLoadRequest frame_request(active_document,
+                                 ResourceRequest(completed_url),
+                                 target.IsEmpty() ? "_blank" : target);
+  frame_request.SetNavigationPolicy(
+      NavigationPolicyForCreateWindow(window_features));
+  frame_request.SetFeaturesForWindowOpen(window_features);
+  frame_request.SetShouldSetOpener(window_features.noopener ? kNeverSetOpener
+                                                            : kMaybeSetOpener);
+
+  // Normally, FrameLoader would take care of setting the referrer for a
+  // navigation that is triggered from javascript. However, creating a window
+  // goes through sufficient processing that it eventually enters FrameLoader as
+  // an embedder-initiated navigation.  FrameLoader assumes no responsibility
+  // for generating an embedder-initiated navigation's referrer, so we need to
+  // ensure the proper referrer is set now.
+  // TODO(domfarolino): Stop setting ResourceRequest's HTTP Referrer and store
+  // this is a separate member. See https://crbug.com/850813.
+  frame_request.GetResourceRequest().SetHttpReferrer(
+      SecurityPolicy::GenerateReferrer(active_document->GetReferrerPolicy(),
+                                       completed_url,
+                                       active_document->OutgoingReferrer()));
+
+  frame_request.GetResourceRequest().SetHasUserGesture(
+      LocalFrame::HasTransientUserActivation(GetFrame()));
+  GetFrame()->MaybeLogAdClickNavigation();
+
+  if (const WebInputEvent* input_event = CurrentInputEvent::Get())
+    frame_request.SetInputStartTime(input_event->TimeStamp());
+
   // Get the target frame for the special cases of _top and _parent.
   // In those cases, we schedule a location change right now and return early.
   Frame* target_frame = nullptr;
@@ -1504,25 +1533,23 @@
     }
   }
 
-  if (!target_frame) {
-    return CreateWindow(completed_url, target, window_features,
-                        *incumbent_window, *GetFrame());
-  }
+  bool created = false;
+  if (!target_frame)
+    target_frame = CreateNewWindow(*GetFrame(), frame_request, created);
+  if (!target_frame)
+    return nullptr;
 
   if (!active_document->GetFrame() ||
       !active_document->GetFrame()->CanNavigate(*target_frame)) {
     return nullptr;
   }
 
-  if (!url_string.IsEmpty() &&
+  if ((!completed_url.IsEmpty() || created) &&
       !target_frame->DomWindow()->IsInsecureScriptAccess(*incumbent_window,
                                                          completed_url)) {
-    FrameLoadRequest request(active_document, ResourceRequest(completed_url));
-    request.GetResourceRequest().SetHasUserGesture(
-        LocalFrame::HasTransientUserActivation(GetFrame()));
-    if (const WebInputEvent* input_event = CurrentInputEvent::Get())
-      request.SetInputStartTime(input_event->TimeStamp());
-    target_frame->Navigate(request, WebFrameLoadType::kStandard);
+    frame_request.SetFrameName("_self");
+    frame_request.SetNavigationPolicy(kNavigationPolicyCurrentTab);
+    target_frame->Navigate(frame_request, WebFrameLoadType::kStandard);
   }
 
   // TODO(japhet): window-open-noopener.html?_top and several tests in
@@ -1538,7 +1565,8 @@
 
   if (window_features.noopener)
     return nullptr;
-  target_frame->Client()->SetOpener(GetFrame());
+  if (!created)
+    target_frame->Client()->SetOpener(GetFrame());
   return target_frame->DomWindow();
 }
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
index 5280e25..73210906 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
@@ -237,6 +237,10 @@
     elapsed_time += Now() - start_time;
   }
 
+  // Should be no more samples.
+  VerifyEntries(2u, millisecond_per_frame, millisecond_per_step,
+                expected_percentage);
+
   // If we record, then the next interval is still shorter than the last frame,
   // we should sample again. Set a short sample period to generate 2 events
   // for the next update. The calculation below ensures we get 2 events for
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index a33d2fb..d0cae95 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -324,9 +324,8 @@
 }
 
 void WebFrameWidgetImpl::BeginUpdateLayers() {
-  if (LocalRootImpl()) {
+  if (LocalRootImpl())
     update_layers_start_time_.emplace(CurrentTimeTicks());
-  }
 }
 
 void WebFrameWidgetImpl::EndUpdateLayers() {
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index f90afed..06e907bf 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -70,11 +70,11 @@
 }
 
 void WebViewFrameWidget::BeginUpdateLayers() {
-  web_view_->BeginRafAlignedInput();
+  web_view_->BeginUpdateLayers();
 }
 
 void WebViewFrameWidget::EndUpdateLayers() {
-  web_view_->EndRafAlignedInput();
+  web_view_->EndUpdateLayers();
 }
 
 void WebViewFrameWidget::BeginCommitCompositorFrame() {
diff --git a/third_party/blink/renderer/core/inspector/BUILD.gn b/third_party/blink/renderer/core/inspector/BUILD.gn
index 99953c85..e644e553 100644
--- a/third_party/blink/renderer/core/inspector/BUILD.gn
+++ b/third_party/blink/renderer/core/inspector/BUILD.gn
@@ -12,8 +12,6 @@
 
 blink_core_sources("inspector") {
   sources = [
-    "add_string_to_digestor.cc",
-    "add_string_to_digestor.h",
     "console_message.cc",
     "console_message.h",
     "console_message_storage.cc",
diff --git a/third_party/blink/renderer/core/inspector/add_string_to_digestor.cc b/third_party/blink/renderer/core/inspector/add_string_to_digestor.cc
deleted file mode 100644
index 0876479a..0000000
--- a/third_party/blink/renderer/core/inspector/add_string_to_digestor.cc
+++ /dev/null
@@ -1,18 +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 "third_party/blink/renderer/core/inspector/add_string_to_digestor.h"
-
-#include "third_party/blink/public/platform/web_crypto.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-
-namespace blink {
-
-void AddStringToDigestor(WebCryptoDigestor* digestor, const String& string) {
-  const CString c_string = string.Utf8();
-  digestor->Consume(reinterpret_cast<const unsigned char*>(c_string.data()),
-                    c_string.length());
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/add_string_to_digestor.h b/third_party/blink/renderer/core/inspector/add_string_to_digestor.h
deleted file mode 100644
index f795ad08..0000000
--- a/third_party/blink/renderer/core/inspector/add_string_to_digestor.h
+++ /dev/null
@@ -1,17 +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 THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_ADD_STRING_TO_DIGESTOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_ADD_STRING_TO_DIGESTOR_H_
-
-namespace WTF {
-class String;
-}
-
-namespace blink {
-class WebCryptoDigestor;
-void AddStringToDigestor(WebCryptoDigestor*, const WTF::String&);
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_ADD_STRING_TO_DIGESTOR_H_
diff --git a/third_party/blink/renderer/core/inspector/dom_patch_support.cc b/third_party/blink/renderer/core/inspector/dom_patch_support.cc
index 32ed885..bcae61a5 100644
--- a/third_party/blink/renderer/core/inspector/dom_patch_support.cc
+++ b/third_party/blink/renderer/core/inspector/dom_patch_support.cc
@@ -45,7 +45,6 @@
 #include "third_party/blink/renderer/core/html/html_document.h"
 #include "third_party/blink/renderer/core/html/html_head_element.h"
 #include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
-#include "third_party/blink/renderer/core/inspector/add_string_to_digestor.h"
 #include "third_party/blink/renderer/core/inspector/dom_editor.h"
 #include "third_party/blink/renderer/core/inspector/inspector_history.h"
 #include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h"
@@ -436,45 +435,43 @@
     Node* node,
     UnusedNodesMap* unused_nodes_map) {
   Digest* digest = MakeGarbageCollected<Digest>(node);
-
-  std::unique_ptr<WebCryptoDigestor> digestor =
-      CreateDigestor(kHashAlgorithmSha1);
+  Digestor digestor(kHashAlgorithmSha1);
   DigestValue digest_result;
 
   Node::NodeType node_type = node->getNodeType();
-  digestor->Consume(reinterpret_cast<const unsigned char*>(&node_type),
-                    sizeof(node_type));
-  AddStringToDigestor(digestor.get(), node->nodeName());
-  AddStringToDigestor(digestor.get(), node->nodeValue());
+  digestor.Update(
+      {reinterpret_cast<const uint8_t*>(&node_type), sizeof(node_type)});
+  digestor.UpdateUtf8(node->nodeName());
+  digestor.UpdateUtf8(node->nodeValue());
 
   if (node->IsElementNode()) {
     Element& element = ToElement(*node);
     Node* child = element.firstChild();
     while (child) {
       Digest* child_info = CreateDigest(child, unused_nodes_map);
-      AddStringToDigestor(digestor.get(), child_info->sha1_);
+      digestor.UpdateUtf8(child_info->sha1_);
       child = child->nextSibling();
       digest->children_.push_back(child_info);
     }
 
     AttributeCollection attributes = element.AttributesWithoutUpdate();
     if (!attributes.IsEmpty()) {
-      std::unique_ptr<WebCryptoDigestor> attrs_digestor =
-          CreateDigestor(kHashAlgorithmSha1);
+      Digestor attrs_digestor(kHashAlgorithmSha1);
       for (auto& attribute : attributes) {
-        AddStringToDigestor(attrs_digestor.get(),
-                            attribute.GetName().ToString());
-        AddStringToDigestor(attrs_digestor.get(),
-                            attribute.Value().GetString());
+        attrs_digestor.UpdateUtf8(attribute.GetName().ToString());
+        attrs_digestor.UpdateUtf8(attribute.Value().GetString());
       }
-      FinishDigestor(attrs_digestor.get(), digest_result);
+
+      attrs_digestor.Finish(digest_result);
+      DCHECK(!attrs_digestor.has_failed());
       digest->attrs_sha1_ =
           Base64Encode(reinterpret_cast<const char*>(digest_result.data()), 10);
-      AddStringToDigestor(digestor.get(), digest->attrs_sha1_);
-      digest_result.clear();
+      digestor.UpdateUtf8(digest->attrs_sha1_);
     }
   }
-  FinishDigestor(digestor.get(), digest_result);
+
+  digestor.Finish(digest_result);
+  DCHECK(!digestor.has_failed());
   digest->sha1_ =
       Base64Encode(reinterpret_cast<const char*>(digest_result.data()), 10);
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
index 2745117..4b33f1e 100644
--- a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/core/css/css_style_rule.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/inspector/add_string_to_digestor.h"
 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
 #include "third_party/blink/renderer/core/inspector/inspected_frames.h"
 #include "third_party/blink/renderer/core/inspector/inspector_css_agent.h"
@@ -424,10 +423,9 @@
   Element* element = effect->target();
   HeapVector<Member<CSSStyleDeclaration>> styles =
       css_agent_->MatchingStyles(element);
-  std::unique_ptr<WebCryptoDigestor> digestor =
-      CreateDigestor(kHashAlgorithmSha1);
-  AddStringToDigestor(digestor.get(), type);
-  AddStringToDigestor(digestor.get(), animation.id());
+  Digestor digestor(kHashAlgorithmSha1);
+  digestor.UpdateUtf8(type);
+  digestor.UpdateUtf8(animation.id());
   for (const CSSProperty* property : css_properties) {
     CSSStyleDeclaration* style =
         css_agent_->FindEffectiveDeclaration(*property, styles);
@@ -435,14 +433,13 @@
     if (!style || !style->ParentStyleSheet() || !style->parentRule() ||
         style->parentRule()->type() != CSSRule::kStyleRule)
       continue;
-    AddStringToDigestor(digestor.get(), property->GetPropertyNameString());
-    AddStringToDigestor(digestor.get(),
-                        css_agent_->StyleSheetId(style->ParentStyleSheet()));
-    AddStringToDigestor(digestor.get(),
-                        To<CSSStyleRule>(style->parentRule())->selectorText());
+    digestor.UpdateUtf8(property->GetPropertyNameString());
+    digestor.UpdateUtf8(css_agent_->StyleSheetId(style->ParentStyleSheet()));
+    digestor.UpdateUtf8(To<CSSStyleRule>(style->parentRule())->selectorText());
   }
   DigestValue digest_result;
-  FinishDigestor(digestor.get(), digest_result);
+  digestor.Finish(digest_result);
+  DCHECK(!digestor.has_failed());
   return Base64Encode(reinterpret_cast<const char*>(digest_result.data()), 10);
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
index 985ae21..bb9a94d 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
@@ -236,7 +236,7 @@
 }
 
 Response InspectorDOMSnapshotAgent::getSnapshot(
-    std::unique_ptr<protocol::Array<String>> style_whitelist,
+    std::unique_ptr<protocol::Array<String>> style_filter,
     protocol::Maybe<bool> include_event_listeners,
     protocol::Maybe<bool> include_paint_order,
     protocol::Maybe<bool> include_user_agent_shadow_tree,
@@ -256,15 +256,15 @@
   computed_styles_ =
       protocol::Array<protocol::DOMSnapshot::ComputedStyle>::create();
   computed_styles_map_ = std::make_unique<ComputedStylesMap>();
-  css_property_whitelist_ = std::make_unique<CSSPropertyWhitelist>();
+  css_property_filter_ = std::make_unique<CSSPropertyFilter>();
 
-  // Look up the CSSPropertyIDs for each entry in |style_whitelist|.
-  for (wtf_size_t i = 0; i < style_whitelist->length(); i++) {
-    CSSPropertyID property_id = cssPropertyID(style_whitelist->get(i));
+  // Look up the CSSPropertyIDs for each entry in |style_filter|.
+  for (wtf_size_t i = 0; i < style_filter->length(); i++) {
+    CSSPropertyID property_id = cssPropertyID(style_filter->get(i));
     if (property_id == CSSPropertyID::kInvalid)
       continue;
-    css_property_whitelist_->push_back(
-        std::make_pair(style_whitelist->get(i), property_id));
+    css_property_filter_->push_back(
+        std::make_pair(style_filter->get(i), property_id));
   }
 
   if (include_paint_order.fromMaybe(false)) {
@@ -282,7 +282,7 @@
   *layout_tree_nodes = std::move(layout_tree_nodes_);
   *computed_styles = std::move(computed_styles_);
   computed_styles_map_.reset();
-  css_property_whitelist_.reset();
+  css_property_filter_.reset();
   paint_order_map_.reset();
   return Response::OK();
 }
@@ -296,13 +296,13 @@
   documents_ =
       protocol::Array<protocol::DOMSnapshot::DocumentSnapshot>::create();
 
-  css_property_whitelist_ = std::make_unique<CSSPropertyWhitelist>();
+  css_property_filter_ = std::make_unique<CSSPropertyFilter>();
   // Look up the CSSPropertyIDs for each entry in |computed_styles|.
   for (size_t i = 0; i < computed_styles->length(); i++) {
     CSSPropertyID property_id = cssPropertyID(computed_styles->get(i));
     if (property_id == CSSPropertyID::kInvalid)
       continue;
-    css_property_whitelist_->push_back(
+    css_property_filter_->push_back(
         std::make_pair(computed_styles->get(i), property_id));
   }
 
@@ -318,7 +318,7 @@
   // Extract results from state and reset.
   *documents = std::move(documents_);
   *strings = std::move(strings_);
-  css_property_whitelist_.reset();
+  css_property_filter_.reset();
   string_table_.clear();
   document_order_map_.clear();
   documents_.reset();
@@ -928,7 +928,7 @@
 
   Vector<String> style;
   bool all_properties_empty = true;
-  for (const auto& pair : *css_property_whitelist_) {
+  for (const auto& pair : *css_property_filter_) {
     String value = computed_style_info->GetPropertyValue(pair.second);
     if (!value.IsEmpty())
       all_properties_empty = false;
@@ -951,7 +951,7 @@
     if (style[i].IsEmpty())
       continue;
     style_properties->addItem(protocol::DOMSnapshot::NameValue::create()
-                                  .setName((*css_property_whitelist_)[i].first)
+                                  .setName((*css_property_filter_)[i].first)
                                   .setValue(style[i])
                                   .build());
   }
@@ -969,7 +969,7 @@
   auto* computed_style_info =
       MakeGarbageCollected<CSSComputedStyleDeclaration>(node, true);
   std::unique_ptr<protocol::Array<int>> result = protocol::Array<int>::create();
-  for (const auto& pair : *css_property_whitelist_) {
+  for (const auto& pair : *css_property_filter_) {
     String value = computed_style_info->GetPropertyValue(pair.second);
     result->addItem(AddString(value));
   }
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
index 5e29bd3..d57733c 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
@@ -41,7 +41,7 @@
   protocol::Response enable() override;
   protocol::Response disable() override;
   protocol::Response getSnapshot(
-      std::unique_ptr<protocol::Array<String>> style_whitelist,
+      std::unique_ptr<protocol::Array<String>> style_filter,
       protocol::Maybe<bool> include_event_listeners,
       protocol::Maybe<bool> include_paint_order,
       protocol::Maybe<bool> include_user_agent_shadow_tree,
@@ -118,7 +118,7 @@
   // Returns the index of the ComputedStyle in |computed_styles_| for the given
   // Node. Adds a new ComputedStyle if necessary, but ensures no duplicates are
   // added to |computed_styles_|. Returns -1 if the node has no values for
-  // styles in |style_whitelist_|.
+  // styles in |style_filter_|.
   int GetStyleIndexForNode(Node*);
   std::unique_ptr<protocol::Array<int>> BuildStylesForNode(Node*);
 
@@ -133,7 +133,7 @@
                                          int,
                                          VectorStringHashTraits,
                                          VectorStringHashTraits>;
-  using CSSPropertyWhitelist = Vector<std::pair<String, CSSPropertyID>>;
+  using CSSPropertyFilter = Vector<std::pair<String, CSSPropertyID>>;
   using PaintOrderMap = WTF::HashMap<PaintLayer*, int>;
   using OriginUrlMap = WTF::HashMap<DOMNodeId, String>;
 
@@ -154,8 +154,7 @@
   // Maps a style string vector to an index in |computed_styles_|. Used to avoid
   // duplicate entries in |computed_styles_|.
   std::unique_ptr<ComputedStylesMap> computed_styles_map_;
-  std::unique_ptr<Vector<std::pair<String, CSSPropertyID>>>
-      css_property_whitelist_;
+  std::unique_ptr<CSSPropertyFilter> css_property_filter_;
   // Maps a PaintLayer to its paint order index.
   std::unique_ptr<PaintOrderMap> paint_order_map_;
   int next_paint_order_index_ = 0;
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index bfa42d1b..ed8b5ae8 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -1576,6 +1576,16 @@
     Document* owner_document,
     GlobalObjectReusePolicy global_object_reuse_policy,
     const String& source) {
+  // This is necessary because extensions look at DocumentLoader::url_ when
+  // deciding whether to inject a content script. In the case where the content
+  // script can be injected into blank frames, and an iframe is created with
+  // a javascript url as its src attribute, url_ will be empty here as the
+  // javascript url was run in the context of the initial empty document.
+  // However, the document's url will be about:blank, and content scripts
+  // should be allowed to inject into this document.
+  if (url_.IsEmpty())
+    url_ = BlankURL();
+
   InstallNewDocument(url, nullptr, owner_document, global_object_reuse_policy,
                      MimeType(), response_.TextEncodingName(),
                      InstallNewDocumentReason::kJavascriptURL,
diff --git a/third_party/blink/renderer/core/loader/frame_load_request.h b/third_party/blink/renderer/core/loader/frame_load_request.h
index fdb5216..b8e69efa 100644
--- a/third_party/blink/renderer/core/loader/frame_load_request.h
+++ b/third_party/blink/renderer/core/loader/frame_load_request.h
@@ -29,6 +29,7 @@
 #include "services/network/public/mojom/request_context_frame_type.mojom-shared.h"
 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-blink.h"
 #include "third_party/blink/public/web/web_triggering_event_info.h"
+#include "third_party/blink/public/web/web_window_features.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/frame_types.h"
 #include "third_party/blink/renderer/core/loader/frame_loader_types.h"
@@ -146,6 +147,15 @@
 
   base::TimeTicks GetInputStartTime() const { return input_start_time_; }
 
+  const WebWindowFeatures& GetWindowFeatures() const {
+    return window_features_;
+  }
+  void SetFeaturesForWindowOpen(const WebWindowFeatures& features) {
+    window_features_ = features;
+    is_window_open_ = true;
+  }
+  bool IsWindowOpen() const { return is_window_open_; }
+
  private:
   Member<Document> origin_document_;
   ResourceRequest resource_request_;
@@ -165,6 +175,8 @@
   base::TimeTicks input_start_time_;
   network::mojom::RequestContextFrameType frame_type_ =
       network::mojom::RequestContextFrameType::kNone;
+  WebWindowFeatures window_features_;
+  bool is_window_open_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index cec7e21..2fe7c306 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -797,22 +797,32 @@
   if (!PrepareRequestForThisFrame(request))
     return;
 
-  Frame* target_frame = frame_->FindFrameForNavigation(
-      AtomicString(request.FrameName()), *frame_, url);
+  SetReferrerForFrameRequest(request);
 
-  // Downloads and navigations which specifically target a *new* frame
-  // (e.g. because of a ctrl-click) should ignore the target.
-  bool should_navigate_target_frame =
-      request.GetNavigationPolicy() == kNavigationPolicyCurrentTab;
+  // A GetNavigationPolicy() value other than kNavigationPolicyCurrentTab at
+  // this point indicates that a user event modified the navigation policy
+  // (e.g., a ctrl-click). Let the user's action override any target attribute.
+  if (request.GetNavigationPolicy() == kNavigationPolicyCurrentTab) {
+    Frame* target_frame = frame_->FindFrameForNavigation(
+        AtomicString(request.FrameName()), *frame_, url);
+    if (!target_frame) {
+      request.SetNavigationPolicy(kNavigationPolicyNewForegroundTab);
+      bool created = false;
+      target_frame = CreateNewWindow(*frame_.Get(), request, created);
+      request.SetNavigationPolicy(kNavigationPolicyCurrentTab);
+      if (!target_frame)
+        return;
+    }
 
-  if (target_frame && target_frame != frame_ && should_navigate_target_frame) {
-    bool was_in_same_page = target_frame->GetPage() == frame_->GetPage();
-    request.SetFrameName("_self");
-    target_frame->Navigate(request, frame_load_type);
-    Page* page = target_frame->GetPage();
-    if (!was_in_same_page && page)
-      page->GetChromeClient().Focus(frame_);
-    return;
+    if (target_frame != frame_) {
+      bool was_in_same_page = target_frame->GetPage() == frame_->GetPage();
+      request.SetFrameName("_self");
+      target_frame->Navigate(request, frame_load_type);
+      Page* page = target_frame->GetPage();
+      if (!was_in_same_page && page)
+        page->GetChromeClient().Focus(frame_);
+      return;
+    }
   }
 
   // Block renderer-initiated loads of data: and filesystem: URLs in the top
@@ -837,16 +847,6 @@
     return;
   }
 
-  SetReferrerForFrameRequest(request);
-
-  if (!target_frame && !request.FrameName().IsEmpty() &&
-      should_navigate_target_frame) {
-    request.SetFrameType(network::mojom::RequestContextFrameType::kAuxiliary);
-    request.SetNavigationPolicy(kNavigationPolicyNewForegroundTab);
-    CreateWindowForRequest(request, *frame_);
-    return;  // Navigation will be handled by the new frame/window.
-  }
-
   // TODO(dgozman): merge page dismissal check and FrameNavigationDisabler.
   if (!frame_->IsNavigationAllowed() ||
       frame_->GetDocument()->PageDismissalEventBeingDispatched() !=
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 023516a..dab84de 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -33,19 +33,15 @@
 #include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/frame/from_ad_state.h"
-#include "third_party/blink/public/platform/web_input_event.h"
-#include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/public/web/web_view_client.h"
 #include "third_party/blink/public/web/web_window_features.h"
 #include "third_party/blink/renderer/core/core_initializer.h"
 #include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/events/current_input_event.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/ad_tracker.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
 #include "third_party/blink/renderer/core/frame/frame_client.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
@@ -53,8 +49,6 @@
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
-#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
-#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
 
 namespace blink {
 
@@ -207,20 +201,43 @@
   }
 }
 
-static Frame* CreateNewWindow(LocalFrame& opener_frame,
-                              const FrameLoadRequest& request,
-                              const WebWindowFeatures& features,
-                              bool& created) {
+Frame* CreateNewWindow(LocalFrame& opener_frame,
+                       FrameLoadRequest& request,
+                       bool& created) {
   DCHECK(request.GetResourceRequest().RequestorOrigin() ||
          opener_frame.GetDocument()->Url().IsEmpty());
-  DCHECK_EQ(request.GetFrameType(),
-            network::mojom::RequestContextFrameType::kAuxiliary);
+
+  // Exempting window.open() from this check here is necessary to support a
+  // special policy that will be removed in Chrome 82.
+  // See https://crbug.com/937569
+  if (!request.IsWindowOpen() &&
+      opener_frame.GetDocument()->PageDismissalEventBeingDispatched() !=
+          Document::kNoDismissal) {
+    return nullptr;
+  }
+
+  request.SetFrameType(network::mojom::RequestContextFrameType::kAuxiliary);
 
   const KURL& url = request.GetResourceRequest().Url();
+  if (url.ProtocolIsJavaScript() &&
+      opener_frame.GetDocument()->GetContentSecurityPolicy() &&
+      !ContentSecurityPolicy::ShouldBypassMainWorld(
+          opener_frame.GetDocument())) {
+    String script_source = DecodeURLEscapeSequences(
+        url.GetString(), DecodeURLMode::kUTF8OrIsomorphic);
+
+    if (!opener_frame.GetDocument()->GetContentSecurityPolicy()->AllowInline(
+            ContentSecurityPolicy::InlineType::kNavigation,
+            nullptr /* element */, script_source, String() /* nonce */,
+            opener_frame.GetDocument()->Url(), OrdinalNumber())) {
+      return nullptr;
+    }
+  }
+
+  const WebWindowFeatures& features = request.GetWindowFeatures();
   probe::WindowOpen(opener_frame.GetDocument(), url, request.FrameName(),
                     features,
                     LocalFrame::HasTransientUserActivation(&opener_frame));
-  created = false;
 
   // Sandboxed frames cannot open new auxiliary browsing contexts.
   if (opener_frame.GetDocument()->IsSandboxed(WebSandboxFlags::kPopups)) {
@@ -265,6 +282,16 @@
   if (!page)
     return nullptr;
 
+  auto* new_local_frame = DynamicTo<LocalFrame>(page->MainFrame());
+  if (request.GetShouldSendReferrer() == kMaybeSendReferrer) {
+    // TODO(japhet): Does network::mojom::ReferrerPolicy need to be proagated
+    // for RemoteFrames?
+    if (new_local_frame) {
+      new_local_frame->GetDocument()->SetReferrerPolicy(
+          opener_frame.GetDocument()->GetReferrerPolicy());
+    }
+  }
+
   if (page == old_page) {
     Frame* frame = &opener_frame.Tree().Top();
     if (!opener_frame.CanNavigate(*frame))
@@ -299,122 +326,4 @@
   return &frame;
 }
 
-DOMWindow* CreateWindow(const KURL& completed_url,
-                        const AtomicString& frame_name,
-                        const WebWindowFeatures& window_features,
-                        LocalDOMWindow& incumbent_window,
-                        LocalFrame& opener_frame) {
-  LocalFrame* active_frame = incumbent_window.GetFrame();
-  DCHECK(active_frame);
-
-  if (completed_url.ProtocolIsJavaScript() &&
-      opener_frame.GetDocument()->GetContentSecurityPolicy() &&
-      !ContentSecurityPolicy::ShouldBypassMainWorld(
-          opener_frame.GetDocument())) {
-    String script_source = DecodeURLEscapeSequences(
-        completed_url.GetString(), DecodeURLMode::kUTF8OrIsomorphic);
-
-    if (!opener_frame.GetDocument()->GetContentSecurityPolicy()->AllowInline(
-            ContentSecurityPolicy::InlineType::kNavigation,
-            nullptr /* element */, script_source, String() /* nonce */,
-            opener_frame.GetDocument()->Url(), OrdinalNumber())) {
-      return nullptr;
-    }
-  }
-
-  FrameLoadRequest frame_request(incumbent_window.document(),
-                                 ResourceRequest(completed_url), frame_name);
-  frame_request.SetNavigationPolicy(
-      NavigationPolicyForCreateWindow(window_features));
-  frame_request.SetShouldSetOpener(window_features.noopener ? kNeverSetOpener
-                                                            : kMaybeSetOpener);
-  frame_request.SetFrameType(
-      network::mojom::RequestContextFrameType::kAuxiliary);
-
-  // Normally, FrameLoader would take care of setting the referrer for a
-  // navigation that is triggered from javascript. However, creating a window
-  // goes through sufficient processing that it eventually enters FrameLoader as
-  // an embedder-initiated navigation.  FrameLoader assumes no responsibility
-  // for generating an embedder-initiated navigation's referrer, so we need to
-  // ensure the proper referrer is set now.
-  // TODO(domfarolino): Stop setting ResourceRequest's HTTP Referrer and store
-  // this is a separate member. See https://crbug.com/850813.
-  frame_request.GetResourceRequest().SetHttpReferrer(
-      SecurityPolicy::GenerateReferrer(
-          active_frame->GetDocument()->GetReferrerPolicy(), completed_url,
-          active_frame->GetDocument()->OutgoingReferrer()));
-
-  // Records HasUserGesture before the value is invalidated inside
-  // createWindow(LocalFrame& openerFrame, ...).
-  // This value will be set in ResourceRequest loaded in a new LocalFrame.
-  bool has_user_gesture = LocalFrame::HasTransientUserActivation(&opener_frame);
-  opener_frame.MaybeLogAdClickNavigation();
-
-  // We pass the opener frame for the lookupFrame in case the active frame is
-  // different from the opener frame, and the name references a frame relative
-  // to the opener frame.
-  bool created;
-  Frame* new_frame =
-      CreateNewWindow(opener_frame, frame_request, window_features, created);
-  if (!new_frame)
-    return nullptr;
-  if (new_frame->DomWindow()->IsInsecureScriptAccess(incumbent_window,
-                                                     completed_url))
-    return window_features.noopener ? nullptr : new_frame->DomWindow();
-
-  if (created || !completed_url.IsEmpty()) {
-    FrameLoadRequest request(incumbent_window.document(),
-                             ResourceRequest(completed_url));
-    request.GetResourceRequest().SetHasUserGesture(has_user_gesture);
-    if (const WebInputEvent* input_event = CurrentInputEvent::Get()) {
-      request.SetInputStartTime(input_event->TimeStamp());
-    }
-    new_frame->Navigate(request, WebFrameLoadType::kStandard);
-  }
-  return window_features.noopener ? nullptr : new_frame->DomWindow();
-}
-
-void CreateWindowForRequest(const FrameLoadRequest& request,
-                            LocalFrame& opener_frame) {
-  DCHECK(request.GetResourceRequest().RequestorOrigin() ||
-         (opener_frame.GetDocument() &&
-          opener_frame.GetDocument()->Url().IsEmpty()));
-
-  if (opener_frame.GetDocument()->PageDismissalEventBeingDispatched() !=
-      Document::kNoDismissal)
-    return;
-
-  if (opener_frame.GetDocument() &&
-      opener_frame.GetDocument()->IsSandboxed(WebSandboxFlags::kPopups))
-    return;
-
-  WebWindowFeatures features;
-  features.noopener = request.GetShouldSetOpener() == kNeverSetOpener;
-  bool created;
-  Frame* new_frame = CreateNewWindow(opener_frame, request, features, created);
-  if (!new_frame)
-    return;
-  auto* new_local_frame = DynamicTo<LocalFrame>(new_frame);
-  if (request.GetShouldSendReferrer() == kMaybeSendReferrer) {
-    // TODO(japhet): Does network::mojom::ReferrerPolicy need to be proagated
-    // for RemoteFrames?
-    if (new_local_frame) {
-      new_local_frame->GetDocument()->SetReferrerPolicy(
-          opener_frame.GetDocument()->GetReferrerPolicy());
-    }
-  }
-
-  // TODO(japhet): Form submissions on RemoteFrames don't work yet.
-  FrameLoadRequest new_request(nullptr, request.GetResourceRequest());
-  new_request.SetForm(request.Form());
-  if (const WebInputEvent* input_event = CurrentInputEvent::Get()) {
-    new_request.SetInputStartTime(input_event->TimeStamp());
-  }
-  auto blob_url_token = request.GetBlobURLToken();
-  if (blob_url_token)
-    new_request.SetBlobURLToken(std::move(blob_url_token));
-  if (new_local_frame)
-    new_local_frame->Loader().StartNavigation(new_request);
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/create_window.h b/third_party/blink/renderer/core/page/create_window.h
index 11e635ae4..d11a26b 100644
--- a/third_party/blink/renderer/core/page/create_window.h
+++ b/third_party/blink/renderer/core/page/create_window.h
@@ -28,23 +28,17 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_CREATE_WINDOW_H_
 
 #include "third_party/blink/public/web/web_window_features.h"
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
-// To avoid conflicts with the CreateWindow macro from the Windows SDK...
-#undef CreateWindow
-
 namespace blink {
+class Frame;
 class LocalFrame;
 struct FrameLoadRequest;
 
-DOMWindow* CreateWindow(const KURL& completed_url,
-                        const AtomicString& frame_name,
-                        const WebWindowFeatures&,
-                        LocalDOMWindow& incumbent_window,
-                        LocalFrame& opener_frame);
-
-void CreateWindowForRequest(const FrameLoadRequest&, LocalFrame& opener_frame);
+Frame* CreateNewWindow(LocalFrame& opener_frame,
+                       FrameLoadRequest&,
+                       bool& created);
 
 CORE_EXPORT WebWindowFeatures GetWindowFeaturesFromString(const String&);
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
index a6bf09d..5f5e30e 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
@@ -59,4 +59,12 @@
   }
 }
 
+PositionInFlatTree TextFragmentFinder::FindStart() {
+  return PositionInFlatTree();
+}
+
+PositionInFlatTree TextFragmentFinder::FindEnd() {
+  return PositionInFlatTree();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
index 34db3f7b..207dd4fc 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h
@@ -35,7 +35,8 @@
   Client& client_;
   const TextFragmentSelector selector_;
 
-  String search_text_;
+  PositionInFlatTree FindStart();
+  PositionInFlatTree FindEnd();
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 7580d12..fad2b51 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -1993,7 +1993,8 @@
                             bool override_existing_colors);
   void ClearAppliedTextDecorations();
   void RestoreParentTextDecorations(const ComputedStyle& parent_style);
-  const Vector<AppliedTextDecoration>& AppliedTextDecorations() const;
+  CORE_EXPORT const Vector<AppliedTextDecoration>& AppliedTextDecorations()
+      const;
   TextDecoration TextDecorationsInEffect() const;
 
   // Overflow utility functions.
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index ef2b7527..5d6c628 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -1099,25 +1099,72 @@
   return static_cast<unsigned>(1 << static_cast<int>(text_style_enum));
 }
 
-int32_t AXLayoutObject::GetTextStyle() const {
-  if (!GetLayoutObject())
-    return AXNodeObject::GetTextStyle();
-
+void AXLayoutObject::GetTextStyleAndTextDecorationStyle(
+    int32_t* text_style,
+    ax::mojom::TextDecorationStyle* text_overline_style,
+    ax::mojom::TextDecorationStyle* text_strikethrough_style,
+    ax::mojom::TextDecorationStyle* text_underline_style) const {
+  if (!GetLayoutObject()) {
+    AXNodeObject::GetTextStyleAndTextDecorationStyle(
+        text_style, text_overline_style, text_strikethrough_style,
+        text_underline_style);
+    return;
+  }
   const ComputedStyle* style = GetLayoutObject()->Style();
-  if (!style)
-    return AXNodeObject::GetTextStyle();
+  if (!style) {
+    AXNodeObject::GetTextStyleAndTextDecorationStyle(
+        text_style, text_overline_style, text_strikethrough_style,
+        text_underline_style);
+    return;
+  }
 
-  int32_t text_style = 0;
+  *text_style = 0;
+  *text_overline_style = ax::mojom::TextDecorationStyle::kNone;
+  *text_strikethrough_style = ax::mojom::TextDecorationStyle::kNone;
+  *text_underline_style = ax::mojom::TextDecorationStyle::kNone;
+
   if (style->GetFontWeight() == BoldWeightValue())
-    text_style |= TextStyleFlag(ax::mojom::TextStyle::kBold);
+    *text_style |= TextStyleFlag(ax::mojom::TextStyle::kBold);
   if (style->GetFontDescription().Style() == ItalicSlopeValue())
-    text_style |= TextStyleFlag(ax::mojom::TextStyle::kItalic);
-  if (style->GetTextDecoration() == TextDecoration::kUnderline)
-    text_style |= TextStyleFlag(ax::mojom::TextStyle::kUnderline);
-  if (style->GetTextDecoration() == TextDecoration::kLineThrough)
-    text_style |= TextStyleFlag(ax::mojom::TextStyle::kLineThrough);
+    *text_style |= TextStyleFlag(ax::mojom::TextStyle::kItalic);
 
-  return text_style;
+  for (const auto& decoration : style->AppliedTextDecorations()) {
+    if (EnumHasFlags(decoration.Lines(), TextDecoration::kOverline)) {
+      *text_style |= TextStyleFlag(ax::mojom::TextStyle::kOverline);
+      *text_overline_style =
+          TextDecorationStyleToAXTextDecorationStyle(decoration.Style());
+    }
+    if (EnumHasFlags(decoration.Lines(), TextDecoration::kLineThrough)) {
+      *text_style |= TextStyleFlag(ax::mojom::TextStyle::kLineThrough);
+      *text_strikethrough_style =
+          TextDecorationStyleToAXTextDecorationStyle(decoration.Style());
+    }
+    if (EnumHasFlags(decoration.Lines(), TextDecoration::kUnderline)) {
+      *text_style |= TextStyleFlag(ax::mojom::TextStyle::kUnderline);
+      *text_underline_style =
+          TextDecorationStyleToAXTextDecorationStyle(decoration.Style());
+    }
+  }
+}
+
+ax::mojom::TextDecorationStyle
+AXLayoutObject::TextDecorationStyleToAXTextDecorationStyle(
+    const blink::ETextDecorationStyle text_decoration_style) {
+  switch (text_decoration_style) {
+    case ETextDecorationStyle::kDashed:
+      return ax::mojom::TextDecorationStyle::kDashed;
+    case ETextDecorationStyle::kSolid:
+      return ax::mojom::TextDecorationStyle::kSolid;
+    case ETextDecorationStyle::kDotted:
+      return ax::mojom::TextDecorationStyle::kDotted;
+    case ETextDecorationStyle::kDouble:
+      return ax::mojom::TextDecorationStyle::kDouble;
+    case ETextDecorationStyle::kWavy:
+      return ax::mojom::TextDecorationStyle::kWavy;
+  }
+
+  NOTREACHED();
+  return ax::mojom::TextDecorationStyle::kNone;
 }
 
 //
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
index d3d05a9..1dd5049 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
@@ -109,7 +109,11 @@
   ax::mojom::TextDirection GetTextDirection() const final;
   ax::mojom::TextPosition GetTextPosition() const final;
   int TextLength() const override;
-  int32_t GetTextStyle() const final;
+  void GetTextStyleAndTextDecorationStyle(
+      int32_t* text_style,
+      ax::mojom::TextDecorationStyle* text_overline_style,
+      ax::mojom::TextDecorationStyle* text_strikethrough_style,
+      ax::mojom::TextDecorationStyle* text_underline_style) const final;
 
   // Inline text boxes.
   void LoadInlineTextBoxes() override;
@@ -240,6 +244,10 @@
   bool HasAriaCellRole(Element*) const;
   bool IsPlaceholder() const;
 
+  static ax::mojom::TextDecorationStyle
+  TextDecorationStyleToAXTextDecorationStyle(
+      const ETextDecorationStyle text_decoration_style);
+
   bool is_autofill_available_;
 
   DISALLOW_COPY_AND_ASSIGN(AXLayoutObject);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 3500a9db..c9e07e16 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -695,8 +695,16 @@
   }
   virtual int TextLength() const { return 0; }
 
-  // Bitmask from ax::mojom::TextStyle.
-  virtual int32_t GetTextStyle() const { return 0; }
+  virtual void GetTextStyleAndTextDecorationStyle(
+      int32_t* text_style,
+      ax::mojom::TextDecorationStyle* text_overline_style,
+      ax::mojom::TextDecorationStyle* text_strikethrough_style,
+      ax::mojom::TextDecorationStyle* text_underline_style) const {
+    *text_style = 0;
+    *text_overline_style = ax::mojom::TextDecorationStyle::kNone;
+    *text_strikethrough_style = ax::mojom::TextDecorationStyle::kNone;
+    *text_underline_style = ax::mojom::TextDecorationStyle::kNone;
+  }
 
   virtual AXObjectVector RadioButtonsInGroup() const {
     return AXObjectVector();
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
index aba0caa..08b668f 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet.cc
@@ -16,7 +16,6 @@
 #include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_messaging_proxy.h"
-#include "third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h"
 #include "third_party/blink/renderer/platform/graphics/paint_generated_image.h"
 
 namespace blink {
@@ -142,6 +141,7 @@
 void PaintWorklet::Trace(blink::Visitor* visitor) {
   visitor->Trace(pending_generator_registry_);
   visitor->Trace(document_definition_map_);
+  visitor->Trace(proxy_client_);
   Worklet::Trace(visitor);
   Supplement<LocalDOMWindow>::Trace(visitor);
 }
@@ -158,10 +158,13 @@
         pending_generator_registry_, GetNumberOfGlobalScopes() + 1);
   }
 
-  PaintWorkletProxyClient* proxy_client = PaintWorkletProxyClient::Create(
-      To<Document>(GetExecutionContext()), worklet_id_);
+  if (!proxy_client_) {
+    proxy_client_ = PaintWorkletProxyClient::Create(
+        To<Document>(GetExecutionContext()), worklet_id_);
+  }
+
   auto* worker_clients = MakeGarbageCollected<WorkerClients>();
-  ProvidePaintWorkletProxyClientTo(worker_clients, proxy_client);
+  ProvidePaintWorkletProxyClientTo(worker_clients, proxy_client_);
 
   PaintWorkletMessagingProxy* proxy =
       MakeGarbageCollected<PaintWorkletMessagingProxy>(GetExecutionContext());
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet.h b/third_party/blink/renderer/modules/csspaint/paint_worklet.h
index 6fd1431..df9201c 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet.h
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet.h
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/modules/csspaint/document_paint_definition.h"
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.h"
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_pending_generator_registry.h"
+#include "third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 
@@ -87,6 +88,10 @@
   // paint the image.
   int worklet_id_;
 
+  // The proxy client associated with this PaintWorklet. We keep a reference in
+  // to ensure that all global scopes get the same proxy client.
+  Member<PaintWorkletProxyClient> proxy_client_;
+
   DISALLOW_COPY_AND_ASSIGN(PaintWorklet);
 };
 
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
index ec592cc..8f872e7c 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.cc
@@ -280,7 +280,7 @@
 
   if (PaintWorkletProxyClient* proxy_client =
           PaintWorkletProxyClient::From(Clients())) {
-    proxy_client->SetGlobalScope(this);
+    proxy_client->AddGlobalScope(this);
     registered_ = true;
   }
 }
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
index f6be94d..c7e7b380 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_test.cc
@@ -70,11 +70,8 @@
                                     PaintWorkletProxyClient* proxy_client,
                                     base::WaitableEvent* waitable_event) {
     ASSERT_TRUE(thread->IsCurrentThread());
-    proxy_client->SetGlobalScopeForTesting(
-        static_cast<PaintWorkletGlobalScope*>(
-            To<WorkletGlobalScope>(thread->GlobalScope())));
     CrossThreadPersistent<PaintWorkletGlobalScope> global_scope =
-        proxy_client->global_scope_;
+        To<PaintWorkletGlobalScope>(thread->GlobalScope());
     ScriptState* script_state =
         global_scope->ScriptController()->GetScriptState();
     ASSERT_TRUE(script_state);
@@ -84,8 +81,7 @@
     ScriptState::Scope scope(script_state);
 
     {
-      // registerPaint() with a valid class definition should define an
-      // animator.
+      // registerPaint() with a valid class definition should define a painter.
       String source_code =
           R"JS(
             registerPaint('test', class {
@@ -101,8 +97,8 @@
     }
 
     {
-      // registerPaint() with a null class definition should fail to define
-      // an painter.
+      // registerPaint() with a null class definition should fail to define a
+      // painter.
       String source_code = "registerPaint('null', null);";
       ASSERT_FALSE(global_scope->ScriptController()->Evaluate(
           ScriptSourceCode(source_code), SanitizeScriptErrors::kDoNotSanitize));
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
index 17e16d9..915f8cb 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/workers/worker_thread.h"
 #include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
+#include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
 #include "third_party/blink/renderer/platform/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
 #include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
@@ -46,23 +47,25 @@
   PaintWorkletPainter::Trace(visitor);
 }
 
-void PaintWorkletProxyClient::SetGlobalScopeForTesting(
-    PaintWorkletGlobalScope* global_scope) {
-  DCHECK(global_scope);
-  DCHECK(global_scope->IsContextThread());
-  global_scope_ = global_scope;
-}
-
-void PaintWorkletProxyClient::SetGlobalScope(WorkletGlobalScope* global_scope) {
+void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) {
   DCHECK(global_scope);
   DCHECK(global_scope->IsContextThread());
   if (state_ == RunState::kDisposed)
     return;
   DCHECK(state_ == RunState::kUninitialized);
 
-  global_scope_ = static_cast<PaintWorkletGlobalScope*>(global_scope);
+  global_scopes_.push_back(To<PaintWorkletGlobalScope>(global_scope));
+
+  // Wait for all global scopes to be set before registering.
+  if (global_scopes_.size() < PaintWorklet::kNumGlobalScopes) {
+    return;
+  }
+
+  // All the global scopes that share a single PaintWorkletProxyClient are
+  // running on the same thread, and so we can just grab the task runner from
+  // the last one to call this function and use that.
   scoped_refptr<base::SingleThreadTaskRunner> global_scope_runner =
-      global_scope_->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI);
+      global_scope->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI);
   state_ = RunState::kWorking;
 
   compositor_paintee_->RegisterPaintWorkletPainter(this, global_scope_runner);
@@ -71,28 +74,38 @@
 void PaintWorkletProxyClient::Dispose() {
   if (state_ == RunState::kWorking) {
     compositor_paintee_->UnregisterPaintWorkletPainter(this);
-
-    DCHECK(global_scope_);
-    DCHECK(global_scope_->IsContextThread());
-
-    // At worklet scope termination break the reference cycle between
-    // PaintWorkletGlobalScope and PaintWorkletProxyClient.
-    global_scope_ = nullptr;
   }
-
   compositor_paintee_ = nullptr;
 
-  DCHECK(state_ != RunState::kDisposed);
   state_ = RunState::kDisposed;
+
+  // At worklet scope termination break the reference cycle between
+  // PaintWorkletGlobalScope and PaintWorkletProxyClient.
+  global_scopes_.clear();
 }
 
 sk_sp<PaintRecord> PaintWorkletProxyClient::Paint(
     CompositorPaintWorkletInput* compositor_input) {
-  if (!global_scope_)
+  // TODO: Can this happen? We don't register till all are here.
+  if (global_scopes_.IsEmpty())
     return sk_make_sp<PaintRecord>();
+
+  // PaintWorklets are stateless by spec. There are two ways script might try to
+  // inject state:
+  //   * From one PaintWorklet to another, in the same frame.
+  //   * Inside the same PaintWorklet, across frames.
+  //
+  // To discourage both of these, we randomize selection of the global scope.
+  // TODO(smcgruer): Once we are passing bundles of PaintWorklets here, we
+  // should shuffle the bundle randomly and then assign half to the first global
+  // scope, and half to the rest.
+  DCHECK_EQ(global_scopes_.size(), PaintWorklet::kNumGlobalScopes);
+  PaintWorkletGlobalScope* global_scope =
+      global_scopes_[base::RandInt(0, PaintWorklet::kNumGlobalScopes - 1)];
+
   PaintWorkletInput* input = static_cast<PaintWorkletInput*>(compositor_input);
   CSSPaintDefinition* definition =
-      global_scope_->FindDefinition(input->NameCopy());
+      global_scope->FindDefinition(input->NameCopy());
 
   return definition->Paint(FloatSize(input->GetSize()), input->EffectiveZoom(),
                            nullptr, nullptr);
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
index 94e604da..d7d5f311 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h
@@ -24,8 +24,6 @@
 //
 // This is constructed on the main thread but it is used in the worklet backing
 // thread.
-//
-// TODO(smcgruer): Add the dispatcher logic.
 class MODULES_EXPORT PaintWorkletProxyClient
     : public GarbageCollectedFinalized<PaintWorkletProxyClient>,
       public Supplement<WorkerClients>,
@@ -49,8 +47,11 @@
   int GetWorkletId() const override { return worklet_id_; }
   sk_sp<PaintRecord> Paint(CompositorPaintWorkletInput*) override;
 
-  virtual void SetGlobalScope(WorkletGlobalScope*);
-  void SetGlobalScopeForTesting(PaintWorkletGlobalScope*);
+  virtual void AddGlobalScope(WorkletGlobalScope*);
+  const Vector<CrossThreadPersistent<PaintWorkletGlobalScope>>&
+  GetGlobalScopesForTesting() const {
+    return global_scopes_;
+  }
   void Dispose();
 
   static PaintWorkletProxyClient* From(WorkerClients*);
@@ -60,11 +61,10 @@
   friend class PaintWorkletProxyClientTest;
   FRIEND_TEST_ALL_PREFIXES(PaintWorkletProxyClientTest,
                            PaintWorkletProxyClientConstruction);
-  FRIEND_TEST_ALL_PREFIXES(PaintWorkletProxyClientTest, SetGlobalScope);
 
   scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee_;
   const int worklet_id_;
-  CrossThreadPersistent<PaintWorkletGlobalScope> global_scope_;
+  Vector<CrossThreadPersistent<PaintWorkletGlobalScope>> global_scopes_;
   enum RunState { kUninitialized, kWorking, kDisposed } state_;
 };
 
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
index be13087c..aaa9ea8 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
@@ -15,6 +15,7 @@
 #include "third_party/blink/renderer/core/css/cssom/paint_worklet_input.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
+#include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
 #include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"
 #include "third_party/blink/renderer/modules/worklet/worklet_thread_test_common.h"
 #include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
@@ -34,22 +35,29 @@
     reporting_proxy_ = std::make_unique<WorkerReportingProxy>();
   }
 
-  void SetAndCheckGlobalScope(WorkerThread* thread,
+  void RunAddGlobalScopesTest(WorkerThread* thread,
                               PaintWorkletProxyClient* proxy_client,
                               base::WaitableEvent* waitable_event) {
-    proxy_client->SetGlobalScope(To<WorkletGlobalScope>(thread->GlobalScope()));
-    EXPECT_EQ(proxy_client->global_scope_,
-              To<WorkletGlobalScope>(thread->GlobalScope()));
-    EXPECT_EQ(dispatcher_->painter_map_.size(), 1u);
-    waitable_event->Signal();
-  }
+    // For this test, we cheat and reuse the same global scope object from a
+    // single WorkerThread. In real code these would be different global scopes.
 
-  void SetGlobalScopeForTesting(WorkerThread* thread,
-                                PaintWorkletProxyClient* proxy_client,
-                                base::WaitableEvent* waitable_event) {
-    proxy_client->SetGlobalScopeForTesting(
-        static_cast<PaintWorkletGlobalScope*>(
-            To<WorkletGlobalScope>(thread->GlobalScope())));
+    // First, add all but one of the global scopes. The proxy client should not
+    // yet register itself.
+    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopes - 1; i++) {
+      proxy_client->AddGlobalScope(
+          To<WorkletGlobalScope>(thread->GlobalScope()));
+    }
+
+    EXPECT_EQ(proxy_client->global_scopes_.size(),
+              PaintWorklet::kNumGlobalScopes - 1);
+    EXPECT_EQ(dispatcher_->painter_map_.size(), 0u);
+
+    // Now add the final global scope. This should trigger the registration.
+    proxy_client->AddGlobalScope(To<WorkletGlobalScope>(thread->GlobalScope()));
+    EXPECT_EQ(proxy_client->global_scopes_.size(),
+              PaintWorklet::kNumGlobalScopes);
+    EXPECT_EQ(dispatcher_->painter_map_.size(), 1u);
+
     waitable_event->Signal();
   }
 
@@ -57,40 +65,47 @@
       void (PaintWorkletProxyClientTest::*)(WorkerThread*,
                                             PaintWorkletProxyClient*,
                                             base::WaitableEvent*);
-  void RunTestOnWorkletThread(TestCallback callback) {
-    std::unique_ptr<WorkerThread> worklet =
-        CreateThreadAndProvidePaintWorkletProxyClient(
-            &GetDocument(), reporting_proxy_.get(), proxy_client_);
 
+  void RunMultipleGlobalScopeTestsOnWorklet(TestCallback callback) {
+    // PaintWorklet is stateless, and this is enforced via having multiple
+    // global scopes (which are switched between). To mimic the real world,
+    // create multiple WorkerThread for this. Note that the underlying thread
+    // may be shared even though they are unique WorkerThread instances!
+    Vector<std::unique_ptr<WorkerThread>> worklet_threads;
+    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopes; i++) {
+      worklet_threads.push_back(CreateThreadAndProvidePaintWorkletProxyClient(
+          &GetDocument(), reporting_proxy_.get(), proxy_client_));
+    }
+
+    // Now let the test actually run. We only run the test on the first worklet
+    // thread currently; this suffices since they share the proxy.
     base::WaitableEvent waitable_event;
     PostCrossThreadTask(
-        *worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+        *worklet_threads[0]->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
         CrossThreadBind(
             callback, CrossThreadUnretained(this),
-            CrossThreadUnretained(worklet.get()),
+            CrossThreadUnretained(worklet_threads[0].get()),
             CrossThreadPersistent<PaintWorkletProxyClient>(proxy_client_),
             CrossThreadUnretained(&waitable_event)));
     waitable_event.Wait();
     waitable_event.Reset();
 
-    worklet->Terminate();
-    worklet->WaitForShutdownForTesting();
+    // And finally clean up.
+    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopes; i++) {
+      worklet_threads[i]->Terminate();
+      worklet_threads[i]->WaitForShutdownForTesting();
+    }
   }
 
   void RunPaintOnWorklet(WorkerThread* thread,
                          PaintWorkletProxyClient* proxy_client,
                          base::WaitableEvent* waitable_event) {
-    // The "registerPaint" script calls the real SetGlobalScope, so at this
-    // moment all we need is setting the |global_scope_| without any other
-    // things.
-    proxy_client->SetGlobalScopeForTesting(
-        static_cast<PaintWorkletGlobalScope*>(
-            To<WorkletGlobalScope>(thread->GlobalScope())));
-    CrossThreadPersistent<PaintWorkletGlobalScope> global_scope =
-        proxy_client->global_scope_;
-    global_scope->ScriptController()->Evaluate(
-        ScriptSourceCode("registerPaint('foo', class { paint() { } });"),
-        SanitizeScriptErrors::kDoNotSanitize);
+    // Make sure to register the painter on all global scopes.
+    for (const auto& global_scope : proxy_client->GetGlobalScopesForTesting()) {
+      global_scope->ScriptController()->Evaluate(
+          ScriptSourceCode("registerPaint('foo', class { paint() { } });"),
+          SanitizeScriptErrors::kDoNotSanitize);
+    }
 
     PaintWorkletStylePropertyMap::CrossThreadData data;
     scoped_refptr<PaintWorkletInput> input =
@@ -122,22 +137,20 @@
   EXPECT_NE(proxy_client->compositor_paintee_, nullptr);
 }
 
-TEST_F(PaintWorkletProxyClientTest, SetGlobalScope) {
+TEST_F(PaintWorkletProxyClientTest, AddGlobalScope) {
   ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
   // Global scopes must be created on worker threads.
   std::unique_ptr<WorkerThread> worklet_thread =
       CreateThreadAndProvidePaintWorkletProxyClient(
           &GetDocument(), reporting_proxy_.get(), proxy_client_);
 
-  EXPECT_EQ(proxy_client_->global_scope_, nullptr);
+  EXPECT_TRUE(proxy_client_->GetGlobalScopesForTesting().IsEmpty());
 
-  // Register global scopes with proxy client. This step must be performed on
-  // the worker threads.
   base::WaitableEvent waitable_event;
   PostCrossThreadTask(
       *worklet_thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
       CrossThreadBind(
-          &PaintWorkletProxyClientTest::SetAndCheckGlobalScope,
+          &PaintWorkletProxyClientTest::RunAddGlobalScopesTest,
           CrossThreadUnretained(this),
           CrossThreadUnretained(worklet_thread.get()),
           CrossThreadPersistent<PaintWorkletProxyClient>(proxy_client_),
@@ -150,7 +163,8 @@
 
 TEST_F(PaintWorkletProxyClientTest, Paint) {
   ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
-  RunTestOnWorkletThread(&PaintWorkletProxyClientTest::RunPaintOnWorklet);
+  RunMultipleGlobalScopeTestsOnWorklet(
+      &PaintWorkletProxyClientTest::RunPaintOnWorklet);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.cc b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.cc
index aa5e35a..b8483db 100644
--- a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.cc
+++ b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.cc
@@ -53,7 +53,7 @@
 
 constexpr char kJSONLDKeyType[] = "@type";
 constexpr char kJSONLDKeyGraph[] = "@graph";
-bool isWhitelistedType(AtomicString type) {
+bool IsSupportedType(AtomicString type) {
   DEFINE_STATIC_LOCAL(HashSet<AtomicString>, elements,
                       ({// Common types that include addresses.
                         "AutoDealer", "Hotel", "LocalBusiness", "Organization",
@@ -64,9 +64,9 @@
   return type && elements.Contains(type);
 }
 
-void extractEntity(const JSONObject&, Entity&, int recursionLevel);
+void ExtractEntity(const JSONObject&, Entity&, int recursionLevel);
 
-bool parseRepeatedValue(const JSONArray& arr,
+bool ParseRepeatedValue(const JSONArray& arr,
                         Values& values,
                         int recursionLevel) {
   if (arr.size() < 1) {
@@ -136,7 +136,7 @@
       } break;
       case JSONValue::ValueType::kTypeObject:
         values.get_entity_values().push_back(Entity::New());
-        extractEntity(*(JSONObject::Cast(innerVal)),
+        ExtractEntity(*(JSONObject::Cast(innerVal)),
                       *(values.get_entity_values().at(j)), recursionLevel + 1);
         break;
       default:
@@ -146,7 +146,7 @@
   return true;
 }
 
-void extractEntity(const JSONObject& val, Entity& entity, int recursionLevel) {
+void ExtractEntity(const JSONObject& val, Entity& entity, int recursionLevel) {
   if (recursionLevel >= kMaxDepth) {
     return;
   }
@@ -200,12 +200,12 @@
         property->values->set_entity_values(Vector<EntityPtr>());
         property->values->get_entity_values().push_back(Entity::New());
 
-        extractEntity(*(val.GetJSONObject(entry.first)),
+        ExtractEntity(*(val.GetJSONObject(entry.first)),
                       *(property->values->get_entity_values().at(0)),
                       recursionLevel + 1);
       } break;
       case JSONValue::ValueType::kTypeArray:
-        addProperty = parseRepeatedValue(*(val.GetArray(entry.first)),
+        addProperty = ParseRepeatedValue(*(val.GetArray(entry.first)),
                                          *(property->values), recursionLevel);
         break;
       default:
@@ -216,43 +216,43 @@
   }
 }
 
-void extractTopLevelEntity(const JSONObject& val, Vector<EntityPtr>& entities) {
+void ExtractTopLevelEntity(const JSONObject& val, Vector<EntityPtr>& entities) {
   // Now we have a JSONObject which corresponds to a single (possibly nested)
   // entity.
   EntityPtr entity = Entity::New();
   String type;
   val.GetString(kJSONLDKeyType, &type);
-  if (!isWhitelistedType(AtomicString(type))) {
+  if (!IsSupportedType(AtomicString(type))) {
     return;
   }
-  extractEntity(val, *entity, 0);
+  ExtractEntity(val, *entity, 0);
   entities.push_back(std::move(entity));
 }
 
-void extractEntitiesFromArray(const JSONArray& arr,
+void ExtractEntitiesFromArray(const JSONArray& arr,
                               Vector<EntityPtr>& entities) {
   for (wtf_size_t i = 0; i < arr.size(); ++i) {
     const JSONValue* val = arr.at(i);
     if (val->GetType() == JSONValue::ValueType::kTypeObject) {
-      extractTopLevelEntity(*(JSONObject::Cast(val)), entities);
+      ExtractTopLevelEntity(*(JSONObject::Cast(val)), entities);
     }
   }
 }
 
-void extractEntityFromTopLevelObject(const JSONObject& val,
+void ExtractEntityFromTopLevelObject(const JSONObject& val,
                                      Vector<EntityPtr>& entities) {
   const JSONArray* graph = val.GetArray(kJSONLDKeyGraph);
   if (graph) {
-    extractEntitiesFromArray(*graph, entities);
+    ExtractEntitiesFromArray(*graph, entities);
   }
-  extractTopLevelEntity(val, entities);
+  ExtractTopLevelEntity(val, entities);
 }
 
 // ExtractionStatus is used in UMA, hence is append-only.
 // kCount must be the last entry.
 enum ExtractionStatus { kOK, kEmpty, kParseFailure, kWrongType, kCount };
 
-ExtractionStatus extractMetadata(const Element& root,
+ExtractionStatus ExtractMetadata(const Element& root,
                                  Vector<EntityPtr>& entities) {
   for (Element& element : ElementTraversal::DescendantsOf(root)) {
     if (element.HasTagName(html_names::kScriptTag) &&
@@ -264,10 +264,10 @@
       }
       switch (json->GetType()) {
         case JSONValue::ValueType::kTypeArray:
-          extractEntitiesFromArray(*(JSONArray::Cast(json.get())), entities);
+          ExtractEntitiesFromArray(*(JSONArray::Cast(json.get())), entities);
           break;
         case JSONValue::ValueType::kTypeObject:
-          extractEntityFromTopLevelObject(*(JSONObject::Cast(json.get())),
+          ExtractEntityFromTopLevelObject(*(JSONObject::Cast(json.get())),
                                           entities);
           break;
         default:
@@ -283,8 +283,8 @@
 
 }  // namespace
 
-WebPagePtr CopylessPasteExtractor::extract(const Document& document) {
-  TRACE_EVENT0("blink", "CopylessPasteExtractor::extract");
+WebPagePtr CopylessPasteExtractor::Extract(const Document& document) {
+  TRACE_EVENT0("blink", "CopylessPasteExtractor::Extract");
 
   if (!document.GetFrame() || !document.GetFrame()->IsMainFrame())
     return nullptr;
@@ -297,7 +297,7 @@
 
   // Traverse the DOM tree and extract the metadata.
   TimeTicks start_time = CurrentTimeTicks();
-  ExtractionStatus status = extractMetadata(*html, page->entities);
+  ExtractionStatus status = ExtractMetadata(*html, page->entities);
   TimeDelta elapsed_time = CurrentTimeTicks() - start_time;
 
   DEFINE_STATIC_LOCAL(EnumerationHistogram, status_histogram,
@@ -306,14 +306,14 @@
 
   if (status != kOK) {
     DEFINE_STATIC_LOCAL(
-        CustomCountHistogram, extractionHistogram,
+        CustomCountHistogram, extraction_histogram,
         ("CopylessPaste.ExtractionFailedUs", 1, 1000 * 1000, 50));
-    extractionHistogram.CountMicroseconds(elapsed_time);
+    extraction_histogram.CountMicroseconds(elapsed_time);
     return nullptr;
   }
-  DEFINE_STATIC_LOCAL(CustomCountHistogram, extractionHistogram,
+  DEFINE_STATIC_LOCAL(CustomCountHistogram, extraction_histogram,
                       ("CopylessPaste.ExtractionUs", 1, 1000 * 1000, 50));
-  extractionHistogram.CountMicroseconds(elapsed_time);
+  extraction_histogram.CountMicroseconds(elapsed_time);
 
   page->url = document.Url();
   page->title = document.title();
diff --git a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.h b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.h
index dcb8a4e..7adc74a 100644
--- a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.h
+++ b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor.h
@@ -20,7 +20,7 @@
   STATIC_ONLY(CopylessPasteExtractor);
 
  public:
-  static mojom::document_metadata::blink::WebPagePtr extract(const Document&);
+  static mojom::document_metadata::blink::WebPagePtr Extract(const Document&);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor_test.cc b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor_test.cc
index 20a22753..001f9e9 100644
--- a/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor_test.cc
+++ b/third_party/blink/renderer/modules/document_metadata/copyless_paste_extractor_test.cc
@@ -37,7 +37,7 @@
   }
 
   WebPagePtr Extract() {
-    return CopylessPasteExtractor::extract(GetDocument());
+    return CopylessPasteExtractor::Extract(GetDocument());
   }
 
   void SetHTMLInnerHTML(const String&);
@@ -46,15 +46,15 @@
 
   void SetTitle(const String&);
 
-  PropertyPtr createStringProperty(const String& name, const String& value);
+  PropertyPtr CreateStringProperty(const String& name, const String& value);
 
-  PropertyPtr createBooleanProperty(const String& name, const bool& value);
+  PropertyPtr CreateBooleanProperty(const String& name, const bool& value);
 
-  PropertyPtr createLongProperty(const String& name, const int64_t& value);
+  PropertyPtr CreateLongProperty(const String& name, const int64_t& value);
 
-  PropertyPtr createEntityProperty(const String& name, EntityPtr value);
+  PropertyPtr CreateEntityProperty(const String& name, EntityPtr value);
 
-  WebPagePtr createWebPage(const String& url, const String& title);
+  WebPagePtr CreateWebPage(const String& url, const String& title);
 };
 
 void CopylessPasteExtractorTest::SetHTMLInnerHTML(const String& html_content) {
@@ -69,7 +69,7 @@
   GetDocument().setTitle(title);
 }
 
-PropertyPtr CopylessPasteExtractorTest::createStringProperty(
+PropertyPtr CopylessPasteExtractorTest::CreateStringProperty(
     const String& name,
     const String& value) {
   PropertyPtr property = Property::New();
@@ -79,7 +79,7 @@
   return property;
 }
 
-PropertyPtr CopylessPasteExtractorTest::createBooleanProperty(
+PropertyPtr CopylessPasteExtractorTest::CreateBooleanProperty(
     const String& name,
     const bool& value) {
   PropertyPtr property = Property::New();
@@ -89,7 +89,7 @@
   return property;
 }
 
-PropertyPtr CopylessPasteExtractorTest::createLongProperty(
+PropertyPtr CopylessPasteExtractorTest::CreateLongProperty(
     const String& name,
     const int64_t& value) {
   PropertyPtr property = Property::New();
@@ -99,7 +99,7 @@
   return property;
 }
 
-PropertyPtr CopylessPasteExtractorTest::createEntityProperty(const String& name,
+PropertyPtr CopylessPasteExtractorTest::CreateEntityProperty(const String& name,
                                                              EntityPtr value) {
   PropertyPtr property = Property::New();
   property->name = name;
@@ -109,7 +109,7 @@
   return property;
 }
 
-WebPagePtr CopylessPasteExtractorTest::createWebPage(const String& url,
+WebPagePtr CopylessPasteExtractorTest::CreateWebPage(const String& url,
                                                      const String& title) {
   WebPagePtr page = WebPage::New();
   page->url = blink::KURL(url);
@@ -140,12 +140,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Special characters for ya >_<;"));
+      CreateStringProperty("name", "Special characters for ya >_<;"));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -171,12 +171,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Special characters for ya >_<;"));
+      CreateStringProperty("name", "Special characters for ya >_<;"));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -201,11 +201,11 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
-  restaurant->properties.push_back(createBooleanProperty("open", true));
+  restaurant->properties.push_back(CreateBooleanProperty("open", true));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -230,11 +230,11 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
-  restaurant->properties.push_back(createLongProperty("long", 1ll));
+  restaurant->properties.push_back(CreateLongProperty("long", 1ll));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -259,11 +259,11 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
-  restaurant->properties.push_back(createStringProperty("double", "1.5"));
+  restaurant->properties.push_back(CreateStringProperty("double", "1.5"));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -307,13 +307,13 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   for (int i = 0; i < 3; ++i) {
     EntityPtr restaurant = Entity::New();
     restaurant->type = "Restaurant";
     restaurant->properties.push_back(
-        createStringProperty("name", "Special characters for ya >_<;"));
+        CreateStringProperty("name", "Special characters for ya >_<;"));
 
     expected->entities.push_back(std::move(restaurant));
   }
@@ -344,22 +344,22 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Ye ol greasy diner"));
+      CreateStringProperty("name", "Ye ol greasy diner"));
 
   EntityPtr address = Entity::New();
   address->type = "Thing";
   address->properties.push_back(
-      createStringProperty("streetAddress", "123 Big Oak Road"));
+      CreateStringProperty("streetAddress", "123 Big Oak Road"));
   address->properties.push_back(
-      createStringProperty("addressLocality", "San Francisco"));
+      CreateStringProperty("addressLocality", "San Francisco"));
 
   restaurant->properties.push_back(
-      createEntityProperty("address", std::move(address)));
+      CreateEntityProperty("address", std::move(address)));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -384,7 +384,7 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
@@ -434,12 +434,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Ye ol greasy diner"));
+      CreateStringProperty("name", "Ye ol greasy diner"));
 
   PropertyPtr addressProperty = Property::New();
   addressProperty->name = "address";
@@ -449,9 +449,9 @@
     EntityPtr address = Entity::New();
     address->type = "Thing";
     address->properties.push_back(
-        createStringProperty("streetAddress", "123 Big Oak Road"));
+        CreateStringProperty("streetAddress", "123 Big Oak Road"));
     address->properties.push_back(
-        createStringProperty("addressLocality", "San Francisco"));
+        CreateStringProperty("addressLocality", "San Francisco"));
     addressProperty->values->get_entity_values().push_back(std::move(address));
   }
   restaurant->properties.push_back(std::move(addressProperty));
@@ -488,12 +488,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", maxLengthString.ToString()));
+      CreateStringProperty("name", maxLengthString.ToString()));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -517,7 +517,7 @@
   ASSERT_TRUE(extracted.is_null());
 }
 
-TEST_F(CopylessPasteExtractorTest, enforceTypeWhitelist) {
+TEST_F(CopylessPasteExtractorTest, UnhandledTypeIgnored) {
   SetHTMLInnerHTML(
       "<body>"
       "<script type=\"application/ld+json\">"
@@ -565,7 +565,7 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
@@ -612,14 +612,14 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
 
   for (int i = 0; i < 19; ++i) {
     restaurant->properties.push_back(
-        createStringProperty(String::Number(i), "a"));
+        CreateStringProperty(String::Number(i), "a"));
   }
 
   expected->entities.push_back(std::move(restaurant));
@@ -645,7 +645,7 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
@@ -674,7 +674,7 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
@@ -703,7 +703,7 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
@@ -741,12 +741,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Ye ol greasy diner"));
+      CreateStringProperty("name", "Ye ol greasy diner"));
 
   EntityPtr entity1 = Entity::New();
   entity1->type = "Thing";
@@ -757,12 +757,12 @@
   EntityPtr entity3 = Entity::New();
   entity3->type = "Thing";
 
-  entity2->properties.push_back(createEntityProperty("3", std::move(entity3)));
+  entity2->properties.push_back(CreateEntityProperty("3", std::move(entity3)));
 
-  entity1->properties.push_back(createEntityProperty("2", std::move(entity2)));
+  entity1->properties.push_back(CreateEntityProperty("2", std::move(entity2)));
 
   restaurant->properties.push_back(
-      createEntityProperty("1", std::move(entity1)));
+      CreateEntityProperty("1", std::move(entity1)));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
@@ -794,12 +794,12 @@
   ASSERT_FALSE(extracted.is_null());
 
   WebPagePtr expected =
-      createWebPage("http://www.test.com/", "My neat website about cool stuff");
+      CreateWebPage("http://www.test.com/", "My neat website about cool stuff");
 
   EntityPtr restaurant = Entity::New();
   restaurant->type = "Restaurant";
   restaurant->properties.push_back(
-      createStringProperty("name", "Ye ol greasy diner"));
+      CreateStringProperty("name", "Ye ol greasy diner"));
 
   EntityPtr entity1 = Entity::New();
   entity1->type = "Thing";
@@ -810,14 +810,14 @@
   EntityPtr entity3 = Entity::New();
   entity3->type = "Thing";
 
-  entity3->properties.push_back(createLongProperty("4", 5));
+  entity3->properties.push_back(CreateLongProperty("4", 5));
 
-  entity2->properties.push_back(createEntityProperty("3", std::move(entity3)));
+  entity2->properties.push_back(CreateEntityProperty("3", std::move(entity3)));
 
-  entity1->properties.push_back(createEntityProperty("2", std::move(entity2)));
+  entity1->properties.push_back(CreateEntityProperty("2", std::move(entity2)));
 
   restaurant->properties.push_back(
-      createEntityProperty("1", std::move(entity1)));
+      CreateEntityProperty("1", std::move(entity1)));
 
   expected->entities.push_back(std::move(restaurant));
   EXPECT_EQ(expected, extracted);
diff --git a/third_party/blink/renderer/modules/document_metadata/copyless_paste_server.cc b/third_party/blink/renderer/modules/document_metadata/copyless_paste_server.cc
index a7a8f1bf..08d9bc0 100644
--- a/third_party/blink/renderer/modules/document_metadata/copyless_paste_server.cc
+++ b/third_party/blink/renderer/modules/document_metadata/copyless_paste_server.cc
@@ -32,7 +32,7 @@
     return;
   }
   std::move(callback).Run(
-      CopylessPasteExtractor::extract(*frame_->GetDocument()));
+      CopylessPasteExtractor::Extract(*frame_->GetDocument()));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/exported/web_ax_object.cc b/third_party/blink/renderer/modules/exported/web_ax_object.cc
index 0d4d396..b74634c 100644
--- a/third_party/blink/renderer/modules/exported/web_ax_object.cc
+++ b/third_party/blink/renderer/modules/exported/web_ax_object.cc
@@ -1029,11 +1029,21 @@
   return private_->GetTextPosition();
 }
 
-int WebAXObject::TextStyle() const {
-  if (IsDetached())
-    return 0;
-
-  return private_->GetTextStyle();
+void WebAXObject::GetTextStyleAndTextDecorationStyle(
+    int32_t* text_style,
+    ax::mojom::TextDecorationStyle* text_overline_style,
+    ax::mojom::TextDecorationStyle* text_strikethrough_style,
+    ax::mojom::TextDecorationStyle* text_underline_style) const {
+  if (IsDetached()) {
+    *text_style = 0;
+    *text_overline_style = ax::mojom::TextDecorationStyle::kNone;
+    *text_strikethrough_style = ax::mojom::TextDecorationStyle::kNone;
+    *text_underline_style = ax::mojom::TextDecorationStyle::kNone;
+    return;
+  }
+  private_->GetTextStyleAndTextDecorationStyle(text_style, text_overline_style,
+                                               text_strikethrough_style,
+                                               text_underline_style);
 }
 
 WebURL WebAXObject::Url() const {
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad.cc b/third_party/blink/renderer/modules/gamepad/gamepad.cc
index e8c5d87..6d842d6a 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad.cc
+++ b/third_party/blink/renderer/modules/gamepad/gamepad.cc
@@ -27,12 +27,10 @@
 
 #include <algorithm>
 
-#include "third_party/blink/renderer/modules/gamepad/navigator_gamepad.h"
-
 namespace blink {
 
-Gamepad::Gamepad(NavigatorGamepad* navigator_gamepad, unsigned index)
-    : navigator_gamepad_(navigator_gamepad),
+Gamepad::Gamepad(Client* client, unsigned index)
+    : client_(client),
       index_(index),
       timestamp_(0.0),
       has_vibration_actuator_(false),
@@ -87,11 +85,7 @@
 }
 
 GamepadHapticActuator* Gamepad::vibrationActuator() const {
-  // A disconnected gamepad may share the same index as a newly-connected
-  // gamepad. Return nullptr for disconnected gamepads to avoid returning the
-  // actuator for the connected gamepad.
-  return connected_ ? navigator_gamepad_->GetVibrationActuator(index_)
-                    : nullptr;
+  return client_->GetVibrationActuatorForGamepad(*this);
 }
 
 void Gamepad::SetVibrationActuatorInfo(
@@ -130,7 +124,7 @@
 }
 
 void Gamepad::Trace(blink::Visitor* visitor) {
-  visitor->Trace(navigator_gamepad_);
+  visitor->Trace(client_);
   visitor->Trace(buttons_);
   visitor->Trace(pose_);
   ScriptWrappable::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad.h b/third_party/blink/renderer/modules/gamepad/gamepad.h
index d98a65a..955d448b 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad.h
+++ b/third_party/blink/renderer/modules/gamepad/gamepad.h
@@ -39,13 +39,19 @@
 
 namespace blink {
 
-class NavigatorGamepad;
-
 class MODULES_EXPORT Gamepad final : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  explicit Gamepad(NavigatorGamepad* navigator_gamepad, unsigned index);
+  // Objects implementing this interface are garbage-collected.
+  class Client : public GarbageCollectedMixin {
+   public:
+    virtual GamepadHapticActuator* GetVibrationActuatorForGamepad(
+        const Gamepad&) = 0;
+    virtual ~Client() = default;
+  };
+
+  explicit Gamepad(Client* client, unsigned index);
   ~Gamepad() override;
 
   typedef Vector<double> DoubleVector;
@@ -91,8 +97,7 @@
   void Trace(blink::Visitor*) override;
 
  private:
-  // A reference to the NavigatorGamepad that created this gamepad.
-  Member<NavigatorGamepad> navigator_gamepad_;
+  Member<Client> client_;
 
   // A string identifying the gamepad model.
   String id_;
diff --git a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
index 2b825836..bc3319b 100644
--- a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
+++ b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
@@ -192,17 +192,22 @@
   }
 }
 
-GamepadHapticActuator* NavigatorGamepad::GetVibrationActuator(
-    uint32_t pad_index) {
-  auto* gamepad = gamepads_->item(pad_index);
-  if (!gamepad || !gamepad->HasVibrationActuator())
+GamepadHapticActuator* NavigatorGamepad::GetVibrationActuatorForGamepad(
+    const Gamepad& gamepad) {
+  if (!gamepad.connected()) {
     return nullptr;
+  }
+
+  uint32_t pad_index = gamepad.index();
+  if (!gamepad.HasVibrationActuator()) {
+    return nullptr;
+  }
 
   if (!vibration_actuators_[pad_index]) {
     ExecutionContext* context =
         DomWindow() ? DomWindow()->GetExecutionContext() : nullptr;
     auto* actuator = GamepadHapticActuator::Create(context, pad_index);
-    actuator->SetType(gamepad->GetVibrationActuatorType());
+    actuator->SetType(gamepad.GetVibrationActuatorType());
     vibration_actuators_[pad_index] = actuator;
   }
   return vibration_actuators_[pad_index].Get();
@@ -216,6 +221,7 @@
   Supplement<Navigator>::Trace(visitor);
   DOMWindowClient::Trace(visitor);
   PlatformEventController::Trace(visitor);
+  Gamepad::Client::Trace(visitor);
 }
 
 bool NavigatorGamepad::StartUpdatingIfAttached() {
diff --git a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
index 6422223..f901c378 100644
--- a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
+++ b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/navigator.h"
 #include "third_party/blink/renderer/core/frame/platform_event_controller.h"
+#include "third_party/blink/renderer/modules/gamepad/gamepad.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
@@ -43,7 +44,6 @@
 namespace blink {
 
 class Document;
-class Gamepad;
 class GamepadDispatcher;
 class GamepadHapticActuator;
 class GamepadList;
@@ -54,7 +54,8 @@
       public Supplement<Navigator>,
       public DOMWindowClient,
       public PlatformEventController,
-      public LocalDOMWindow::EventListenerObserver {
+      public LocalDOMWindow::EventListenerObserver,
+      public Gamepad::Client {
   USING_GARBAGE_COLLECTED_MIXIN(NavigatorGamepad);
 
  public:
@@ -69,8 +70,6 @@
   static GamepadList* getGamepads(Navigator&);
   GamepadList* Gamepads();
 
-  GamepadHapticActuator* GetVibrationActuator(uint32_t pad_index);
-
   void Trace(blink::Visitor*) override;
 
  private:
@@ -96,6 +95,10 @@
   void DidRemoveEventListener(LocalDOMWindow*, const AtomicString&) override;
   void DidRemoveAllEventListeners(LocalDOMWindow*) override;
 
+  // Gamepad::Client
+  GamepadHapticActuator* GetVibrationActuatorForGamepad(
+      const Gamepad&) override;
+
   // A reference to the buffer containing the last-received gamepad state. May
   // be nullptr if no data has been received yet. Do not overwrite this buffer
   // as it may have already been returned to the page. Instead, write to
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index b340388..1218dfc 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -199,6 +199,7 @@
     "//third_party/blink/renderer/platform/heap:blink_heap_buildflags",
     "//third_party/blink/renderer/platform/network:make_generated",
     "//third_party/blink/renderer/platform/wtf",
+    "//third_party/boringssl",
     "//third_party/iccjpeg",
     "//third_party/libpng",
     "//third_party/libwebp",
@@ -1450,6 +1451,7 @@
     "//base/allocator:buildflags",
     "//components/viz/client",
     "//components/viz/common",
+    "//crypto",
     "//device/vr/public/mojom:mojom_blink",
     "//gin",
     "//media",
diff --git a/third_party/blink/renderer/platform/DEPS b/third_party/blink/renderer/platform/DEPS
index 4d7a6859..4c6a6c2 100644
--- a/third_party/blink/renderer/platform/DEPS
+++ b/third_party/blink/renderer/platform/DEPS
@@ -76,6 +76,10 @@
 ]
 
 specific_include_rules = {
+    "crypto\.(cc|h)": [
+        "+crypto",
+        "+third_party/boringssl/src/include",
+    ],
     "web_url_error\.cc": [
         "+net/base/net_errors.h"
     ],
diff --git a/third_party/blink/renderer/platform/crypto.cc b/third_party/blink/renderer/platform/crypto.cc
index 2e522aa1..d19f51e 100644
--- a/third_party/blink/renderer/platform/crypto.cc
+++ b/third_party/blink/renderer/platform/crypto.cc
@@ -4,67 +4,77 @@
 
 #include "third_party/blink/renderer/platform/crypto.h"
 
-#include <memory>
-#include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/public/platform/web_crypto.h"
-#include "third_party/blink/public/platform/web_crypto_algorithm.h"
+#include "base/numerics/safe_conversions.h"
+#include "crypto/openssl_util.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
 
 namespace blink {
 
-static WebCryptoAlgorithmId ToWebCryptoAlgorithmId(HashAlgorithm algorithm) {
+Digestor::Digestor(HashAlgorithm algorithm) {
+  crypto::EnsureOpenSSLInit();
+  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+  const EVP_MD* evp_md = nullptr;
   switch (algorithm) {
     case kHashAlgorithmSha1:
-      return kWebCryptoAlgorithmIdSha1;
+      evp_md = EVP_sha1();
+      break;
     case kHashAlgorithmSha256:
-      return kWebCryptoAlgorithmIdSha256;
+      evp_md = EVP_sha256();
+      break;
     case kHashAlgorithmSha384:
-      return kWebCryptoAlgorithmIdSha384;
+      evp_md = EVP_sha384();
+      break;
     case kHashAlgorithmSha512:
-      return kWebCryptoAlgorithmIdSha512;
-  };
+      evp_md = EVP_sha512();
+      break;
+  }
 
-  NOTREACHED();
-  return kWebCryptoAlgorithmIdSha256;
+  has_failed_ =
+      !evp_md || !EVP_DigestInit_ex(digest_context_.get(), evp_md, nullptr);
+}
+
+Digestor::~Digestor() = default;
+
+bool Digestor::Update(base::span<const uint8_t> data) {
+  if (has_failed_)
+    return false;
+
+  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  has_failed_ =
+      !EVP_DigestUpdate(digest_context_.get(), data.data(), data.size());
+  return !has_failed_;
+}
+
+bool Digestor::UpdateUtf8(const String& string, WTF::UTF8ConversionMode mode) {
+  StringUTF8Adaptor utf8(string, mode);
+  return Update(base::as_bytes(base::make_span(utf8)));
+}
+
+bool Digestor::Finish(DigestValue& digest_result) {
+  if (has_failed_)
+    return false;
+
+  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+  const size_t expected_size = EVP_MD_CTX_size(digest_context_.get());
+  DCHECK_LE(expected_size, static_cast<size_t>(EVP_MAX_MD_SIZE));
+  digest_result.resize(base::checked_cast<wtf_size_t>(expected_size));
+
+  unsigned result_size;
+  has_failed_ = !EVP_DigestFinal_ex(digest_context_.get(), digest_result.data(),
+                                    &result_size) ||
+                result_size != expected_size;
+  return !has_failed_;
 }
 
 bool ComputeDigest(HashAlgorithm algorithm,
                    const char* digestable,
                    size_t length,
                    DigestValue& digest_result) {
-  WebCryptoAlgorithmId algorithm_id = ToWebCryptoAlgorithmId(algorithm);
-  WebCrypto* crypto = Platform::Current()->Crypto();
-  unsigned char* result;
-  unsigned result_size;
-
-  DCHECK(crypto);
-
-  std::unique_ptr<WebCryptoDigestor> digestor =
-      crypto->CreateDigestor(algorithm_id);
-  DCHECK(digestor);
-  if (!digestor->Consume(reinterpret_cast<const unsigned char*>(digestable),
-                         length) ||
-      !digestor->Finish(result, result_size))
-    return false;
-
-  digest_result.Append(static_cast<uint8_t*>(result), result_size);
-  return true;
-}
-
-std::unique_ptr<WebCryptoDigestor> CreateDigestor(HashAlgorithm algorithm) {
-  return Platform::Current()->Crypto()->CreateDigestor(
-      ToWebCryptoAlgorithmId(algorithm));
-}
-
-void FinishDigestor(WebCryptoDigestor* digestor, DigestValue& digest_result) {
-  unsigned char* result = nullptr;
-  unsigned result_size = 0;
-
-  if (!digestor->Finish(result, result_size))
-    return;
-
-  DCHECK(result);
-
-  digest_result.Append(static_cast<uint8_t*>(result), result_size);
+  Digestor digestor(algorithm);
+  digestor.Update(base::as_bytes(base::make_span(digestable, length)));
+  digestor.Finish(digest_result);
+  return !digestor.has_failed();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/crypto.h b/third_party/blink/renderer/platform/crypto.h
index 03a6372a..09258ac 100644
--- a/third_party/blink/renderer/platform/crypto.h
+++ b/third_party/blink/renderer/platform/crypto.h
@@ -2,29 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(jww) The original Blink-style header guard for this file conflicts with
-// the header guard in Source/modules/crypto/Crypto.h, so this is a
-// Chromium-style header guard instead. There is now a bug
-// (https://crbug.com/360121) to track a proposal to change all header guards
-// to a similar style. Thus, whenever that is resolved, this header guard
-// should be changed to whatever style is agreed upon.
-#ifndef SOURCE_PLATFORM_CRYPTO_H_
-#define SOURCE_PLATFORM_CRYPTO_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_CRYPTO_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_CRYPTO_H_
 
-#include <memory>
-#include "third_party/blink/public/platform/web_crypto.h"
+#include "base/containers/span.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_set.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hasher.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "third_party/boringssl/src/include/openssl/digest.h"
 
 namespace blink {
 
 static const size_t kMaxDigestSize = 64;
 typedef Vector<uint8_t, kMaxDigestSize> DigestValue;
 
-const size_t kSha1HashSize = 20;
 enum HashAlgorithm {
   kHashAlgorithmSha1,
   kHashAlgorithmSha256,
@@ -36,11 +30,25 @@
                                    const char* digestable,
                                    size_t length,
                                    DigestValue& digest_result);
-// Note: this will never return null.
-PLATFORM_EXPORT std::unique_ptr<WebCryptoDigestor> CreateDigestor(
-    HashAlgorithm);
-PLATFORM_EXPORT void FinishDigestor(WebCryptoDigestor*,
-                                    DigestValue& digest_result);
+
+class PLATFORM_EXPORT Digestor {
+ public:
+  explicit Digestor(HashAlgorithm);
+  ~Digestor();
+
+  bool has_failed() const { return has_failed_; }
+
+  // Return false on failure. These do nothing once the |has_failed_| flag is
+  // set. This object cannot be reused; do not update it after Finish.
+  bool Update(base::span<const uint8_t>);
+  bool UpdateUtf8(const String&,
+                  WTF::UTF8ConversionMode = WTF::kLenientUTF8Conversion);
+  bool Finish(DigestValue&);
+
+ private:
+  bssl::ScopedEVP_MD_CTX digest_context_;
+  bool has_failed_ = false;
+};
 
 }  // namespace blink
 
@@ -74,4 +82,5 @@
 };
 
 }  // namespace WTF
-#endif  // SOURCE_PLATFORM_CRYPTO_H_
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_CRYPTO_H_
diff --git a/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc b/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc
index c2f4782..ad6e774 100644
--- a/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler_test.cc
@@ -7,6 +7,7 @@
 #include <array>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_crypto.h"
 #include "third_party/blink/renderer/platform/crypto.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -15,56 +16,6 @@
 
 namespace {
 
-class MockSha256WebCryptoDigestor : public WebCryptoDigestor {
- public:
-  bool Consume(const unsigned char* data, unsigned data_size) override {
-    String key(data, data_size);
-
-    auto it = kMapOfHashes.find(key);
-
-    if (it != kMapOfHashes.end()) {
-      hash_exists_ = true;
-      hash_ = it->value;
-    }
-
-    return hash_exists_;
-  }
-
-  bool Finish(unsigned char*& result_data,
-              unsigned& result_data_size) override {
-    if (hash_exists_) {
-      result_data = hash_.data();
-      result_data_size = hash_.size();
-    }
-    return hash_exists_;
-  }
-
- private:
-  Vector<unsigned char> hash_;
-  bool hash_exists_;
-
-  HashMap<String, Vector<unsigned char>> kMapOfHashes = {
-      {"source1",
-       Vector<unsigned char>{0xc4, 0xd5, 0xe4, 0x35, 0x74, 0x89, 0x3c, 0x3c,
-                             0xc3, 0xd4, 0xba, 0xba, 0x65, 0x58, 0x92, 0x48,
-                             0x47, 0x9a, 0x9f, 0xbf, 0xaf, 0x1f, 0x60, 0x8e,
-                             0xb1, 0x54, 0x1e, 0xc0, 0xc6, 0xfe, 0x63, 0x6f}},
-      {"source2",
-       Vector<unsigned char>{0x99, 0x2f, 0x4e, 0xb2, 0x41, 0xee, 0x6e, 0xef,
-                             0xe4, 0x92, 0x80, 0x25, 0xa2, 0x74, 0x7d, 0xb0,
-                             0x8b, 0x91, 0x98, 0x34, 0xc9, 0x3c, 0x5f, 0x57,
-                             0x41, 0x72, 0x5f, 0xa2, 0x6b, 0x63, 0x38, 0x41}}};
-};
-
-// Mock WebCrypto implementation for digest calculation.
-class MockDigestWebCrypto : public WebCrypto {
-  std::unique_ptr<WebCryptoDigestor> CreateDigestor(
-      WebCryptoAlgorithmId algorithm_id) override {
-    EXPECT_EQ(algorithm_id, WebCryptoAlgorithmId::kWebCryptoAlgorithmIdSha256);
-    return std::make_unique<MockSha256WebCryptoDigestor>();
-  }
-};
-
 // Structure holding cache metadata sent to the platform.
 struct CacheMetadataEntry {
   CacheMetadataEntry(const WebURL& url,
@@ -87,8 +38,6 @@
   SourceKeyedCachedMetadataHandlerMockPlatform() {}
   ~SourceKeyedCachedMetadataHandlerMockPlatform() override = default;
 
-  WebCrypto* Crypto() override { return &mock_web_crypto_; }
-
   void CacheMetadata(blink::mojom::CodeCacheType cache_type,
                      const WebURL& url,
                      base::Time response_time,
@@ -118,7 +67,6 @@
   }
 
  private:
-  MockDigestWebCrypto mock_web_crypto_;
   Vector<CacheMetadataEntry> cache_entries_;
 };
 
diff --git a/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc b/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc
index 3f62128..447a3d7 100644
--- a/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc
+++ b/third_party/blink/renderer/platform/loader/subresource_integrity_test.cc
@@ -32,22 +32,6 @@
 namespace blink {
 
 static const char kBasicScript[] = "alert('test');";
-static unsigned char kSha256Hash[] = {
-    0x18, 0x01, 0x78, 0xf1, 0x03, 0xa8, 0xc5, 0x1b, 0xee, 0xd2, 0x06,
-    0x40, 0x99, 0x08, 0xaf, 0x51, 0xd2, 0x4f, 0xc8, 0x16, 0x9c, 0xab,
-    0x39, 0xc1, 0x01, 0x7c, 0x27, 0x91, 0xfa, 0x66, 0x41, 0x7e};
-static unsigned char kSha384Hash[] = {
-    0x9d, 0xea, 0x77, 0x5e, 0x9b, 0xe1, 0x53, 0x1a, 0x42, 0x30, 0xe5, 0x57,
-    0x20, 0x53, 0xde, 0x71, 0x38, 0x40, 0xa9, 0xd6, 0x3f, 0xb9, 0x57, 0xa2,
-    0x0f, 0x89, 0x17, 0x4a, 0xa5, 0xe9, 0xc7, 0x46, 0x09, 0x51, 0x65, 0x38,
-    0x7d, 0x34, 0xda, 0x16, 0x07, 0x22, 0x4e, 0xe6, 0x64, 0xed, 0xf9, 0x84};
-static unsigned char kSha512Hash[] = {
-    0x4d, 0x79, 0x09, 0xc3, 0x5f, 0x0f, 0xaa, 0x55, 0x65, 0x11, 0x45,
-    0xd7, 0x8d, 0xe5, 0xdb, 0x19, 0xeb, 0x68, 0xa7, 0x54, 0xca, 0x07,
-    0x7c, 0x18, 0x40, 0x8a, 0x75, 0xfe, 0x28, 0x71, 0x08, 0xe1, 0x46,
-    0x51, 0xf1, 0xbd, 0x4d, 0x83, 0x9a, 0x03, 0x53, 0x25, 0x92, 0x94,
-    0xc0, 0xa9, 0x25, 0x7a, 0xc9, 0xa7, 0xaf, 0x2c, 0xef, 0x13, 0x8f,
-    0x9a, 0x60, 0x1f, 0x52, 0x66, 0x67, 0xef, 0x88, 0xb4};
 static const char kSha256Integrity[] =
     "sha256-GAF48QOoxRvu0gZAmQivUdJPyBacqznBAXwnkfpmQX4=";
 static const char kSha256IntegrityLenientSyntax[] =
@@ -551,29 +535,6 @@
       {url, FetchRequestMode::kCors, FetchResponseType::kDefault, kOk},
   };
 
-  MockWebCryptoDigestorFactory factory_sha256(
-      kBasicScript, strlen(kBasicScript), kSha256Hash, sizeof(kSha256Hash));
-  MockWebCryptoDigestorFactory factory_sha384(
-      kBasicScript, strlen(kBasicScript), kSha384Hash, sizeof(kSha384Hash));
-  MockWebCryptoDigestorFactory factory_sha512(
-      kBasicScript, strlen(kBasicScript), kSha512Hash, sizeof(kSha512Hash));
-
-  CryptoTestingPlatformSupport::SetMockCryptoScope mock_crypto_scope(
-      *platform_.GetTestingPlatformSupport());
-
-  EXPECT_CALL(mock_crypto_scope.MockCrypto(),
-              CreateDigestorProxy(kWebCryptoAlgorithmIdSha256))
-      .WillRepeatedly(testing::InvokeWithoutArgs(
-          &factory_sha256, &MockWebCryptoDigestorFactory::Create));
-  EXPECT_CALL(mock_crypto_scope.MockCrypto(),
-              CreateDigestorProxy(kWebCryptoAlgorithmIdSha384))
-      .WillRepeatedly(testing::InvokeWithoutArgs(
-          &factory_sha384, &MockWebCryptoDigestorFactory::Create));
-  EXPECT_CALL(mock_crypto_scope.MockCrypto(),
-              CreateDigestorProxy(kWebCryptoAlgorithmIdSha512))
-      .WillRepeatedly(testing::InvokeWithoutArgs(
-          &factory_sha512, &MockWebCryptoDigestorFactory::Create));
-
   for (const auto& test : cases) {
     SCOPED_TRACE(testing::Message()
                  << ", target: " << test.url.BaseAsString()
diff --git a/third_party/blink/renderer/platform/testing/mock_web_crypto.cc b/third_party/blink/renderer/platform/testing/mock_web_crypto.cc
index 7b0ecee..a4997ec 100644
--- a/third_party/blink/renderer/platform/testing/mock_web_crypto.cc
+++ b/third_party/blink/renderer/platform/testing/mock_web_crypto.cc
@@ -11,12 +11,6 @@
 
 namespace blink {
 
-using testing::_;
-using testing::DoAll;
-using testing::InSequence;
-using testing::Return;
-using testing::SetArgReferee;
-
 // MemEq(p, len) expects memcmp(arg, p, len) == 0, where |arg| is the argument
 // to be matched.
 MATCHER_P2(MemEq,
@@ -28,43 +22,4 @@
   return memcmp(arg, p, len) == 0;
 }
 
-void MockWebCryptoDigestor::ExpectConsumeAndFinish(const void* input_data,
-                                                   unsigned input_length,
-                                                   void* output_data,
-                                                   unsigned output_length) {
-  InSequence s;
-
-  // Consume should be called with a memory region equal to |input_data|.
-  EXPECT_CALL(*this, Consume(MemEq(input_data, input_length), input_length))
-      .WillOnce(Return(true));
-
-  // Finish(unsigned char*& result_data, unsigned& result_data_size) {
-  //   result_data = output_data;
-  //   result_data_size = output_length;
-  //   return true;
-  // }
-  EXPECT_CALL(*this, Finish(_, _))
-      .WillOnce(
-          DoAll(SetArgReferee<0>(static_cast<unsigned char*>(output_data)),
-                SetArgReferee<1>(output_length), Return(true)));
-}
-
-MockWebCryptoDigestorFactory::MockWebCryptoDigestorFactory(
-    const void* input_data,
-    unsigned input_length,
-    void* output_data,
-    unsigned output_length)
-    : input_data_(input_data),
-      input_length_(input_length),
-      output_data_(output_data),
-      output_length_(output_length) {}
-
-MockWebCryptoDigestor* MockWebCryptoDigestorFactory::Create() {
-  std::unique_ptr<MockWebCryptoDigestor> digestor(
-      MockWebCryptoDigestor::Create());
-  digestor->ExpectConsumeAndFinish(input_data_, input_length_, output_data_,
-                                   output_length_);
-  return digestor.release();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/testing/mock_web_crypto.h b/third_party/blink/renderer/platform/testing/mock_web_crypto.h
index 11f839df..86a601c 100644
--- a/third_party/blink/renderer/platform/testing/mock_web_crypto.h
+++ b/third_party/blink/renderer/platform/testing/mock_web_crypto.h
@@ -100,7 +100,6 @@
                     WebCryptoKeyUsageMask,
                     WebCryptoResult,
                     scoped_refptr<base::SingleThreadTaskRunner>));
-  MOCK_METHOD1(CreateDigestorProxy, WebCryptoDigestor*(WebCryptoAlgorithmId));
   MOCK_METHOD7(DeserializeKeyForClone,
                bool(const WebCryptoKeyAlgorithm&,
                     WebCryptoKeyType,
@@ -113,53 +112,9 @@
                bool(const WebCryptoKey&, WebVector<unsigned char>&));
 
  protected:
-  std::unique_ptr<WebCryptoDigestor> CreateDigestor(
-      WebCryptoAlgorithmId id) override {
-    return std::unique_ptr<WebCryptoDigestor>(CreateDigestorProxy(id));
-  }
-
   DISALLOW_COPY_AND_ASSIGN(MockWebCrypto);
 };
 
-class MockWebCryptoDigestor : public WebCryptoDigestor {
- public:
-  ~MockWebCryptoDigestor() override = default;
-
-  static MockWebCryptoDigestor* Create() {
-    return new testing::StrictMock<MockWebCryptoDigestor>();
-  }
-
-  void ExpectConsumeAndFinish(const void* input_data,
-                              unsigned input_length,
-                              void* output_data,
-                              unsigned output_length);
-
-  MOCK_METHOD2(Consume, bool(const unsigned char*, unsigned));
-  MOCK_METHOD2(Finish, bool(unsigned char*&, unsigned&));
-
- protected:
-  MockWebCryptoDigestor() = default;
-
-  DISALLOW_COPY_AND_ASSIGN(MockWebCryptoDigestor);
-};
-
-class MockWebCryptoDigestorFactory final {
-  STACK_ALLOCATED();
-
- public:
-  MockWebCryptoDigestorFactory(const void* input_data,
-                               unsigned input_length,
-                               void* output_data,
-                               unsigned output_length);
-  MockWebCryptoDigestor* Create();
-
- private:
-  const void* const input_data_;
-  const unsigned input_length_;
-  void* const output_data_;
-  const unsigned output_length_;
-};
-
 }  // namespace blink
 
 #endif
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 8471582..bfdbbfd7 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -451,6 +451,69 @@
 crbug.com/949909 external/wpt/css/css-text-decor/text-emphasis-style-open-001.xht [ Failure ]
 crbug.com/949909 external/wpt/css/css-text-decor/text-emphasis-style-shape-001.xht [ Failure ]
 crbug.com/949909 external/wpt/css/css-text-decor/text-emphasis-style-string-001.xht [ Failure ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-073-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-072-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-071-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-020-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-076-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-075-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-021-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-022-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-074-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-underline-position-019-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-048-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-046a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-082-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-048a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-085-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-044-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-097a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-001-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-090-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-092-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-095-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-095a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-040a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-096a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-002-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-045-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-091-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-092a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-004-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-096-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-040-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-003-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-091a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-097-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-041-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-049-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-line-090a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-090a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-046a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-091-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-048a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-092a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-096-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-048-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-003-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-091a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-002-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-090-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-092-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-095a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-049-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-041-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-040a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-097-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-001-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-045-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-082-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-096a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-044-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-004-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-085-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-097a-manual.html [ Skip ]
+crbug.com/949909 external/wpt/css/css-text-decor/text-decoration-040-manual.html [ Skip ]
 
 crbug.com/722825 media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html [ Timeout Pass ]
 crbug.com/722825 virtual/video-surface-layer/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html [ Timeout Pass ]
@@ -3065,69 +3128,6 @@
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html [ Timeout ]
 crbug.com/626703 [ Retina ] external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-negative-duration.html [ Timeout ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-073-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-072-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-071-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-020-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-076-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-075-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-021-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-022-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-074-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-underline-position-019-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-048-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-046a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-082-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-048a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-085-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-044-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-097a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-001-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-090-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-092-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-095-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-095a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-040a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-096a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-002-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-045-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-091-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-092a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-004-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-096-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-040-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-003-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-091a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-097-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-041-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-049-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-line-090a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-090a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-046a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-091-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-048a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-092a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-096-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-048-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-003-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-091a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-002-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-090-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-092-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-095a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-049-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-041-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-040a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-097-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-001-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-045-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-082-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-096a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-044-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-004-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-085-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-097a-manual.html [ Skip ]
-crbug.com/626703 external/wpt/css/css-text-decor/text-decoration-040-manual.html [ Skip ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/media-elements/src_object_blob.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/service-workers/service-worker/websocket.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/websockets/Secure-Close-4999-reason.any.html [ Timeout ]
@@ -4277,6 +4277,10 @@
 crbug.com/943487 external/wpt/wasm/webapi/origin.sub.any.serviceworker.html [ Timeout Pass ]
 crbug.com/943487 external/wpt/wasm/webapi/rejected-arg.any.sharedworker.html [ Timeout Pass ]
 
+# Shared memory growth temporarily disabled.
+crbug.com/951795 external/wpt/wasm/jsapi/grow.any.html [ Pass Failure ]
+crbug.com/951795 external/wpt/wasm/jsapi/grow.any.worker.html [ Pass Failure ]
+
 crbug.com/792435 external/wpt/css/css-multicol/multicol-rule-004.xht [ Failure ]
 crbug.com/792437 external/wpt/css/css-multicol/multicol-rule-inset-000.xht [ Failure ]
 crbug.com/792437 external/wpt/css/css-multicol/multicol-rule-outset-000.xht [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index 9137c65..b9d36aa 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -145670,6 +145670,11 @@
      {}
     ]
    ],
+   "css/css-text-decor/parsing/text-decoration-line-valid-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "css/css-text-decor/reference/line-through-vertical-ref.html": [
     [
      {}
@@ -232972,6 +232977,18 @@
      {}
     ]
    ],
+   "css/css-text-decor/parsing/text-decoration-line-invalid.html": [
+    [
+     "css/css-text-decor/parsing/text-decoration-line-invalid.html",
+     {}
+    ]
+   ],
+   "css/css-text-decor/parsing/text-decoration-line-valid.html": [
+    [
+     "css/css-text-decor/parsing/text-decoration-line-valid.html",
+     {}
+    ]
+   ],
    "css/css-text-decor/text-decoration-serialization.tentative.html": [
     [
      "css/css-text-decor/text-decoration-serialization.tentative.html",
@@ -239006,12 +239023,6 @@
      {}
     ]
    ],
-   "css/cssom/stylesheet-deleterule-error.html": [
-    [
-     "css/cssom/stylesheet-deleterule-error.html",
-     {}
-    ]
-   ],
    "css/cssom/stylesheet-same-origin.sub.html": [
     [
      "css/cssom/stylesheet-same-origin.sub.html",
@@ -392565,6 +392576,18 @@
    "633c5c00392711f1fe1911a07f9cf53c3cd702e9",
    "reftest"
   ],
+  "css/css-text-decor/parsing/text-decoration-line-invalid.html": [
+   "ec8d792c0a803dc726b01e762602b717befc5426",
+   "testharness"
+  ],
+  "css/css-text-decor/parsing/text-decoration-line-valid-expected.txt": [
+   "caf82fb7d2c3bfcd254b51b135aa0e5a09a03415",
+   "support"
+  ],
+  "css/css-text-decor/parsing/text-decoration-line-valid.html": [
+   "3dd2d0c834ec8c7340b093a4c7a1272fb35a8a26",
+   "testharness"
+  ],
   "css/css-text-decor/reference/line-through-vertical-ref.html": [
    "979512787a18ec9cbed7e9baf4b2cbd57ab99d33",
    "support"
@@ -417393,10 +417416,6 @@
    "e86a9a16e3bc9c0a22277996ad1fbb5f273d4bdd",
    "testharness"
   ],
-  "css/cssom/stylesheet-deleterule-error.html": [
-   "e01aa015c737d59e90923795fa730e9599e1ee6e",
-   "testharness"
-  ],
   "css/cssom/stylesheet-replacedata-dynamic-ref.html": [
    "bc9cadebf15d720e9c89b8072c0ef36eca962343",
    "support"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-invalid.html
new file mode 100644
index 0000000..ec8d792
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-invalid.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Decoration Test: Parsing text-decoration-line with invalid values</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#text-decoration-line-property">
+<meta name="assert" content="text-decoration-line supports only the grammar 'none | [ underline || overline || line-through || blink ] | spelling-error | grammar-error'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+test_invalid_value("text-decoration-line", "auto");
+test_invalid_value("text-decoration-line", "null");
+test_invalid_value("text-decoration-line", "noone");
+test_invalid_value("text-decoration-line", "under-line");
+test_invalid_value("text-decoration-line", "over-line");
+test_invalid_value("text-decoration-line", "linethrough");
+test_invalid_value("text-decoration-line", "none underline");
+test_invalid_value("text-decoration-line", "none spelling-error");
+test_invalid_value("text-decoration-line", "underline underline");
+test_invalid_value("text-decoration-line", "underline none overline");
+test_invalid_value("text-decoration-line", "blink line-through blink");
+test_invalid_value("text-decoration-line", "spelling-error overline");
+test_invalid_value("text-decoration-line", "spelling-error grammar-error");
+test_invalid_value("text-decoration-line", "blink underline line-through grammar-error");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid-expected.txt
new file mode 100644
index 0000000..caf82fb7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid-expected.txt
@@ -0,0 +1,71 @@
+This is a testharness.js-based test.
+Found 67 tests; 16 PASS, 51 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS e.style['text-decoration-line'] = "none" should set the property value
+PASS e.style['text-decoration-line'] = "underline" should set the property value
+PASS e.style['text-decoration-line'] = "overline" should set the property value
+PASS e.style['text-decoration-line'] = "line-through" should set the property value
+PASS e.style['text-decoration-line'] = "blink" should set the property value
+PASS e.style['text-decoration-line'] = "underline overline" should set the property value
+FAIL e.style['text-decoration-line'] = "overline underline" should set the property value assert_equals: serialization should be canonical expected "underline overline" but got "overline underline"
+PASS e.style['text-decoration-line'] = "underline line-through" should set the property value
+FAIL e.style['text-decoration-line'] = "line-through underline" should set the property value assert_equals: serialization should be canonical expected "underline line-through" but got "line-through underline"
+PASS e.style['text-decoration-line'] = "underline blink" should set the property value
+FAIL e.style['text-decoration-line'] = "blink underline" should set the property value assert_equals: serialization should be canonical expected "underline blink" but got "blink underline"
+PASS e.style['text-decoration-line'] = "overline line-through" should set the property value
+FAIL e.style['text-decoration-line'] = "line-through overline" should set the property value assert_equals: serialization should be canonical expected "overline line-through" but got "line-through overline"
+PASS e.style['text-decoration-line'] = "overline blink" should set the property value
+FAIL e.style['text-decoration-line'] = "blink overline" should set the property value assert_equals: serialization should be canonical expected "overline blink" but got "blink overline"
+PASS e.style['text-decoration-line'] = "line-through blink" should set the property value
+FAIL e.style['text-decoration-line'] = "blink line-through" should set the property value assert_equals: serialization should be canonical expected "line-through blink" but got "blink line-through"
+PASS e.style['text-decoration-line'] = "underline overline line-through" should set the property value
+FAIL e.style['text-decoration-line'] = "underline line-through overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through" but got "underline line-through overline"
+FAIL e.style['text-decoration-line'] = "overline underline line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through" but got "overline underline line-through"
+FAIL e.style['text-decoration-line'] = "overline line-through underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through" but got "overline line-through underline"
+FAIL e.style['text-decoration-line'] = "line-through underline overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through" but got "line-through underline overline"
+FAIL e.style['text-decoration-line'] = "line-through overline underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through" but got "line-through overline underline"
+PASS e.style['text-decoration-line'] = "underline overline blink" should set the property value
+FAIL e.style['text-decoration-line'] = "underline blink overline" should set the property value assert_equals: serialization should be canonical expected "underline overline blink" but got "underline blink overline"
+FAIL e.style['text-decoration-line'] = "overline underline blink" should set the property value assert_equals: serialization should be canonical expected "underline overline blink" but got "overline underline blink"
+FAIL e.style['text-decoration-line'] = "overline blink underline" should set the property value assert_equals: serialization should be canonical expected "underline overline blink" but got "overline blink underline"
+FAIL e.style['text-decoration-line'] = "blink underline overline" should set the property value assert_equals: serialization should be canonical expected "underline overline blink" but got "blink underline overline"
+FAIL e.style['text-decoration-line'] = "blink overline underline" should set the property value assert_equals: serialization should be canonical expected "underline overline blink" but got "blink overline underline"
+PASS e.style['text-decoration-line'] = "underline line-through blink" should set the property value
+FAIL e.style['text-decoration-line'] = "underline blink line-through" should set the property value assert_equals: serialization should be canonical expected "underline line-through blink" but got "underline blink line-through"
+FAIL e.style['text-decoration-line'] = "line-through underline blink" should set the property value assert_equals: serialization should be canonical expected "underline line-through blink" but got "line-through underline blink"
+FAIL e.style['text-decoration-line'] = "line-through blink underline" should set the property value assert_equals: serialization should be canonical expected "underline line-through blink" but got "line-through blink underline"
+FAIL e.style['text-decoration-line'] = "blink underline line-through" should set the property value assert_equals: serialization should be canonical expected "underline line-through blink" but got "blink underline line-through"
+FAIL e.style['text-decoration-line'] = "blink line-through underline" should set the property value assert_equals: serialization should be canonical expected "underline line-through blink" but got "blink line-through underline"
+PASS e.style['text-decoration-line'] = "overline line-through blink" should set the property value
+FAIL e.style['text-decoration-line'] = "overline blink line-through" should set the property value assert_equals: serialization should be canonical expected "overline line-through blink" but got "overline blink line-through"
+FAIL e.style['text-decoration-line'] = "line-through overline blink" should set the property value assert_equals: serialization should be canonical expected "overline line-through blink" but got "line-through overline blink"
+FAIL e.style['text-decoration-line'] = "line-through blink overline" should set the property value assert_equals: serialization should be canonical expected "overline line-through blink" but got "line-through blink overline"
+FAIL e.style['text-decoration-line'] = "blink overline line-through" should set the property value assert_equals: serialization should be canonical expected "overline line-through blink" but got "blink overline line-through"
+FAIL e.style['text-decoration-line'] = "blink line-through overline" should set the property value assert_equals: serialization should be canonical expected "overline line-through blink" but got "blink line-through overline"
+PASS e.style['text-decoration-line'] = "underline overline line-through blink" should set the property value
+FAIL e.style['text-decoration-line'] = "underline overline blink line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "underline overline blink line-through"
+FAIL e.style['text-decoration-line'] = "underline line-through overline blink" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "underline line-through overline blink"
+FAIL e.style['text-decoration-line'] = "underline line-through blink overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "underline line-through blink overline"
+FAIL e.style['text-decoration-line'] = "underline blink overline line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "underline blink overline line-through"
+FAIL e.style['text-decoration-line'] = "underline blink line-through overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "underline blink line-through overline"
+FAIL e.style['text-decoration-line'] = "overline underline line-through blink" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline underline line-through blink"
+FAIL e.style['text-decoration-line'] = "overline underline blink line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline underline blink line-through"
+FAIL e.style['text-decoration-line'] = "overline line-through underline blink" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline line-through underline blink"
+FAIL e.style['text-decoration-line'] = "overline line-through blink underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline line-through blink underline"
+FAIL e.style['text-decoration-line'] = "overline blink underline line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline blink underline line-through"
+FAIL e.style['text-decoration-line'] = "overline blink line-through underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "overline blink line-through underline"
+FAIL e.style['text-decoration-line'] = "line-through underline overline blink" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through underline overline blink"
+FAIL e.style['text-decoration-line'] = "line-through underline blink overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through underline blink overline"
+FAIL e.style['text-decoration-line'] = "line-through overline underline blink" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through overline underline blink"
+FAIL e.style['text-decoration-line'] = "line-through overline blink underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through overline blink underline"
+FAIL e.style['text-decoration-line'] = "line-through blink underline overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through blink underline overline"
+FAIL e.style['text-decoration-line'] = "line-through blink overline underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "line-through blink overline underline"
+FAIL e.style['text-decoration-line'] = "blink underline overline line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink underline overline line-through"
+FAIL e.style['text-decoration-line'] = "blink underline line-through overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink underline line-through overline"
+FAIL e.style['text-decoration-line'] = "blink overline underline line-through" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink overline underline line-through"
+FAIL e.style['text-decoration-line'] = "blink overline line-through underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink overline line-through underline"
+FAIL e.style['text-decoration-line'] = "blink line-through underline overline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink line-through underline overline"
+FAIL e.style['text-decoration-line'] = "blink line-through overline underline" should set the property value assert_equals: serialization should be canonical expected "underline overline line-through blink" but got "blink line-through overline underline"
+FAIL e.style['text-decoration-line'] = "spelling-error" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['text-decoration-line'] = "grammar-error" should set the property value assert_not_equals: property should be set got disallowed value ""
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid.html
new file mode 100644
index 0000000..3dd2d0c8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/parsing/text-decoration-line-valid.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text Decoration Test: Parsing text-decoration-line with valid values</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#text-decoration-line-property">
+<meta name="assert" content="text-decoration-line supports the full grammar 'none | [ underline || overline || line-through || blink ] | spelling-error | grammar-error'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<script>
+// none
+test_valid_value("text-decoration-line", "none");
+
+// underline || overline || line-through || blink
+test_valid_value("text-decoration-line", "underline");
+test_valid_value("text-decoration-line", "overline");
+test_valid_value("text-decoration-line", "line-through");
+test_valid_value("text-decoration-line", "blink");
+test_valid_value("text-decoration-line", "underline overline");
+test_valid_value("text-decoration-line", "overline underline", "underline overline");
+test_valid_value("text-decoration-line", "underline line-through");
+test_valid_value("text-decoration-line", "line-through underline", "underline line-through");
+test_valid_value("text-decoration-line", "underline blink");
+test_valid_value("text-decoration-line", "blink underline", "underline blink");
+test_valid_value("text-decoration-line", "overline line-through");
+test_valid_value("text-decoration-line", "line-through overline", "overline line-through");
+test_valid_value("text-decoration-line", "overline blink");
+test_valid_value("text-decoration-line", "blink overline", "overline blink");
+test_valid_value("text-decoration-line", "line-through blink");
+test_valid_value("text-decoration-line", "blink line-through", "line-through blink");
+test_valid_value("text-decoration-line", "underline overline line-through");
+test_valid_value("text-decoration-line", "underline line-through overline", "underline overline line-through");
+test_valid_value("text-decoration-line", "overline underline line-through", "underline overline line-through");
+test_valid_value("text-decoration-line", "overline line-through underline", "underline overline line-through");
+test_valid_value("text-decoration-line", "line-through underline overline", "underline overline line-through");
+test_valid_value("text-decoration-line", "line-through overline underline", "underline overline line-through");
+test_valid_value("text-decoration-line", "underline overline blink");
+test_valid_value("text-decoration-line", "underline blink overline", "underline overline blink");
+test_valid_value("text-decoration-line", "overline underline blink", "underline overline blink");
+test_valid_value("text-decoration-line", "overline blink underline", "underline overline blink");
+test_valid_value("text-decoration-line", "blink underline overline", "underline overline blink");
+test_valid_value("text-decoration-line", "blink overline underline", "underline overline blink");
+test_valid_value("text-decoration-line", "underline line-through blink");
+test_valid_value("text-decoration-line", "underline blink line-through", "underline line-through blink");
+test_valid_value("text-decoration-line", "line-through underline blink", "underline line-through blink");
+test_valid_value("text-decoration-line", "line-through blink underline", "underline line-through blink");
+test_valid_value("text-decoration-line", "blink underline line-through", "underline line-through blink");
+test_valid_value("text-decoration-line", "blink line-through underline", "underline line-through blink");
+test_valid_value("text-decoration-line", "overline line-through blink");
+test_valid_value("text-decoration-line", "overline blink line-through", "overline line-through blink");
+test_valid_value("text-decoration-line", "line-through overline blink", "overline line-through blink");
+test_valid_value("text-decoration-line", "line-through blink overline", "overline line-through blink");
+test_valid_value("text-decoration-line", "blink overline line-through", "overline line-through blink");
+test_valid_value("text-decoration-line", "blink line-through overline", "overline line-through blink");
+test_valid_value("text-decoration-line", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "underline overline blink line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "underline line-through overline blink", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "underline line-through blink overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "underline blink overline line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "underline blink line-through overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline underline line-through blink", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline underline blink line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline line-through underline blink", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline line-through blink underline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline blink underline line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "overline blink line-through underline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through underline overline blink", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through underline blink overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through overline underline blink", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through overline blink underline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through blink underline overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "line-through blink overline underline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink underline overline line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink underline line-through overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink overline underline line-through", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink overline line-through underline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink line-through underline overline", "underline overline line-through blink");
+test_valid_value("text-decoration-line", "blink line-through overline underline", "underline overline line-through blink");
+
+// spelling-error
+test_valid_value("text-decoration-line", "spelling-error");
+
+// grammar-error
+test_valid_value("text-decoration-line", "grammar-error");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/style-change-events-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/style-change-events-expected.txt
index 3826b238..89e308f 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/style-change-events-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/style-change-events-expected.txt
@@ -1,5 +1,6 @@
 This is a testharness.js-based test.
-FAIL All property keys are recognized assert_in_array: Test property 'pending' should be one of the properties on  Animation value "pending" not in array ["startTime", "currentTime", "playbackRate", "playState", "id", "onfinish", "oncancel", "finish", "play", "pause", "reverse", "cancel", "finished", "ready", "timeline", "effect", "Animation constructor"]
+FAIL All property keys are recognized assert_in_array: Test property 'pending' should be one of the properties on  Animation value "pending" not in array ["effect", "startTime", "currentTime", "playbackRate", "playState", "id", "onfinish", "oncancel", "finish", "play", "pause", "reverse", "cancel", "finished", "ready", "timeline", "Animation constructor"]
+PASS Animation.effect does NOT trigger a style change event
 PASS Animation.startTime does NOT trigger a style change event
 PASS Animation.currentTime does NOT trigger a style change event
 PASS Animation.playbackRate does NOT trigger a style change event
@@ -15,7 +16,6 @@
 PASS Animation.finished does NOT trigger a style change event
 PASS Animation.ready does NOT trigger a style change event
 FAIL Animation.timeline does NOT trigger a style change event promise_test: Unhandled rejection with value: object "TypeError: Cannot assign to read only property 'timeline' of object '#<Animation>'"
-PASS Animation.effect does NOT trigger a style change event
 PASS Animation.Animation constructor does NOT trigger a style change event
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animation-effects/current-iteration-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animation-effects/current-iteration-expected.txt
deleted file mode 100644
index 06c302aa..0000000
--- a/third_party/blink/web_tests/external/wpt/web-animations/timing-model/animation-effects/current-iteration-expected.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-This is a testharness.js-based test.
-Found 51 tests; 41 PASS, 10 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS Test currentIteration during before and after phase when fill is none
-PASS Test zero iterations: iterations:0 iterationStart:0 duration:0 delay:1 fill:both
-PASS Test zero iterations: iterations:0 iterationStart:0 duration:100 delay:1 fill:both
-PASS Test zero iterations: iterations:0 iterationStart:0 duration:Infinity delay:1 fill:both
-PASS Test zero iterations: iterations:0 iterationStart:2.5 duration:0 delay:1 fill:both
-PASS Test zero iterations: iterations:0 iterationStart:2.5 duration:100 delay:1 fill:both
-FAIL Test zero iterations: iterations:0 iterationStart:2.5 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 2 but got 1.5
-PASS Test zero iterations: iterations:0 iterationStart:3 duration:0 delay:1 fill:both
-PASS Test zero iterations: iterations:0 iterationStart:3 duration:100 delay:1 fill:both
-FAIL Test zero iterations: iterations:0 iterationStart:3 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 3 but got 2
-PASS Test integer iterations: iterations:3 iterationStart:0 duration:0 delay:1 fill:both
-PASS Test integer iterations: iterations:3 iterationStart:0 duration:100 delay:1 fill:both
-PASS Test integer iterations: iterations:3 iterationStart:0 duration:Infinity delay:1 fill:both
-PASS Test integer iterations: iterations:3 iterationStart:2.5 duration:0 delay:1 fill:both
-PASS Test integer iterations: iterations:3 iterationStart:2.5 duration:100 delay:1 fill:both
-FAIL Test integer iterations: iterations:3 iterationStart:2.5 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 2 but got 4.5
-PASS Test integer iterations: iterations:3 iterationStart:3 duration:0 delay:1 fill:both
-PASS Test integer iterations: iterations:3 iterationStart:3 duration:100 delay:1 fill:both
-FAIL Test integer iterations: iterations:3 iterationStart:3 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 3 but got 5
-PASS Test fractional iterations: iterations:3.5 iterationStart:0 duration:0 delay:1 fill:both
-PASS Test fractional iterations: iterations:3.5 iterationStart:0 duration:100 delay:1 fill:both
-PASS Test fractional iterations: iterations:3.5 iterationStart:0 duration:Infinity delay:1 fill:both
-PASS Test fractional iterations: iterations:3.5 iterationStart:2.5 duration:0 delay:1 fill:both
-FAIL Test fractional iterations: iterations:3.5 iterationStart:2.5 duration:100 delay:1 fill:both assert_equals: Value of currentIteration in the after phase expected 5 but got 6
-FAIL Test fractional iterations: iterations:3.5 iterationStart:2.5 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 2 but got 5
-PASS Test fractional iterations: iterations:3.5 iterationStart:3 duration:0 delay:1 fill:both
-PASS Test fractional iterations: iterations:3.5 iterationStart:3 duration:100 delay:1 fill:both
-FAIL Test fractional iterations: iterations:3.5 iterationStart:3 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 3 but got 5.5
-PASS Test infinity iterations: iterations:Infinity iterationStart:0 duration:0 delay:1 fill:both
-PASS Test infinity iterations: iterations:Infinity iterationStart:0 duration:100 delay:1 fill:both
-PASS Test infinity iterations: iterations:Infinity iterationStart:0 duration:Infinity delay:1 fill:both
-PASS Test infinity iterations: iterations:Infinity iterationStart:2.5 duration:0 delay:1 fill:both
-PASS Test infinity iterations: iterations:Infinity iterationStart:2.5 duration:100 delay:1 fill:both
-FAIL Test infinity iterations: iterations:Infinity iterationStart:2.5 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 2 but got Infinity
-PASS Test infinity iterations: iterations:Infinity iterationStart:3 duration:0 delay:1 fill:both
-PASS Test infinity iterations: iterations:Infinity iterationStart:3 duration:100 delay:1 fill:both
-FAIL Test infinity iterations: iterations:Infinity iterationStart:3 duration:Infinity delay:1 fill:both assert_equals: Value of currentIteration in the before phase expected 3 but got Infinity
-PASS Test end delay: duration:100 delay:1 fill:both endDelay:50
-PASS Test end delay: duration:100 delay:1 fill:both endDelay:-50
-PASS Test end delay: duration:100 delay:1 fill:both endDelay:-100
-PASS Test end delay: duration:100 delay:1 fill:both endDelay:-200
-PASS Test end delay: iterationStart:0.5 duration:100 delay:1 fill:both endDelay:50
-FAIL Test end delay: iterationStart:0.5 duration:100 delay:1 fill:both endDelay:-50 assert_equals: Value of currentIteration in the after phase expected 1 but got 0.5
-PASS Test end delay: iterationStart:0.5 duration:100 delay:1 fill:both endDelay:-100
-PASS Test end delay: iterations:2 duration:100 delay:1 fill:both endDelay:-100
-PASS Test end delay: iterations:1 iterationStart:2 duration:100 delay:1 fill:both endDelay:-50
-PASS Test end delay: iterations:1 iterationStart:2 duration:100 delay:1 fill:both endDelay:-100
-PASS Test negative playback rate: duration:1 delay:1 fill:both playbackRate:-1
-PASS Test negative playback rate: duration:1 delay:1 iterations:2 fill:both playbackRate:-1
-PASS Test negative playback rate: duration:0 delay:1 fill:both playbackRate:-1
-PASS Test negative playback rate: duration:0 iterations:0 delay:1 fill:both playbackRate:-1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/fast/events/popup-blocked-to-post-blank-expected.txt b/third_party/blink/web_tests/fast/events/popup-blocked-to-post-blank-expected.txt
index 1c6ee54..5305a4e3 100644
--- a/third_party/blink/web_tests/fast/events/popup-blocked-to-post-blank-expected.txt
+++ b/third_party/blink/web_tests/fast/events/popup-blocked-to-post-blank-expected.txt
@@ -1,2 +1 @@
-CONSOLE ERROR: line 15: Not allowed to navigate top frame to data URL: data:text/html,<script>alert(window)</script>?
 If the POST pop-up was not blocked then there will be an ALERT containing a Window object. Otherwise, the test passes.
diff --git a/third_party/blink/web_tests/fast/frames/sandboxed-iframe-navigation-targetlink-expected.txt b/third_party/blink/web_tests/fast/frames/sandboxed-iframe-navigation-targetlink-expected.txt
index cbcb138..06a635cf 100644
--- a/third_party/blink/web_tests/fast/frames/sandboxed-iframe-navigation-targetlink-expected.txt
+++ b/third_party/blink/web_tests/fast/frames/sandboxed-iframe-navigation-targetlink-expected.txt
@@ -1,5 +1,6 @@
 CONSOLE ERROR: line 18: Unsafe JavaScript attempt to initiate navigation for frame with URL 'about:blank' from frame with URL 'sandboxed-iframe-navigation-targetlink.html'. The frame attempting navigation is sandboxed, and is therefore disallowed from navigating its ancestors.
 
+CONSOLE ERROR: line 18: Blocked opening 'sandboxed-iframe-navigated.html' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set.
 This test verifies that a sandboxed IFrame cannot open a link in another frame using the target attribute of a link.
 
 This is done by loading ten non-sandboxed IFrames, and a single sandboxed one. In addition each of these frames have a target frame (so, 22 frames in total). Expect ten frames to be able to open a link in their corresponding target frame, but the sandboxed one to not be one of them.
diff --git a/third_party/blink/web_tests/http/tests/security/sandboxed-iframe-form-top-expected.txt b/third_party/blink/web_tests/http/tests/security/sandboxed-iframe-form-top-expected.txt
index de8060f..ad4c764 100644
--- a/third_party/blink/web_tests/http/tests/security/sandboxed-iframe-form-top-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/sandboxed-iframe-form-top-expected.txt
@@ -1,5 +1,6 @@
 CONSOLE ERROR: line 8: Unsafe JavaScript attempt to initiate navigation for frame with URL 'http://127.0.0.1:8000/security/sandboxed-iframe-form-top.html' from frame with URL 'http://127.0.0.1:8000/security/resources/sandboxed-iframe-form-top.html'. The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.
 
+CONSOLE ERROR: line 8: Blocked opening 'http://127.0.0.1:8000/security/resources/fail.html?' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set.
 This tests passes if the sandboxed frame cannot navigate the top frame.
 
 PASS
diff --git a/third_party/blink/web_tests/virtual/mouseevent_fractional/fast/events/popup-blocked-to-post-blank-expected.txt b/third_party/blink/web_tests/virtual/mouseevent_fractional/fast/events/popup-blocked-to-post-blank-expected.txt
deleted file mode 100644
index 1c6ee54..0000000
--- a/third_party/blink/web_tests/virtual/mouseevent_fractional/fast/events/popup-blocked-to-post-blank-expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-CONSOLE ERROR: line 15: Not allowed to navigate top frame to data URL: data:text/html,<script>alert(window)</script>?
-If the POST pop-up was not blocked then there will be an ALERT containing a Window object. Otherwise, the test passes.
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 77e5379..461e561 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -42,6 +42,7 @@
 interface Animation : EventTarget
     attribute @@toStringTag
     getter currentTime
+    getter effect
     getter id
     getter oncancel
     getter onfinish
@@ -55,11 +56,18 @@
     method play
     method reverse
     setter currentTime
+    setter effect
     setter id
     setter oncancel
     setter onfinish
     setter playbackRate
     setter startTime
+interface AnimationEffect
+    attribute @@toStringTag
+    method constructor
+    method getComputedTiming
+    method getTiming
+    method updateTiming
 interface AnimationEvent : Event
     attribute @@toStringTag
     getter animationName
@@ -3726,6 +3734,11 @@
     method has
     method keys
     method values
+interface KeyframeEffect : AnimationEffect
+    attribute @@toStringTag
+    getter target
+    method constructor
+    setter target
 interface LinearAccelerationSensor : Accelerometer
     attribute @@toStringTag
     method constructor
diff --git a/third_party/blink/web_tests/virtual/user-activation-v2/fast/events/popup-blocked-to-post-blank-expected.txt b/third_party/blink/web_tests/virtual/user-activation-v2/fast/events/popup-blocked-to-post-blank-expected.txt
deleted file mode 100644
index 1c6ee54..0000000
--- a/third_party/blink/web_tests/virtual/user-activation-v2/fast/events/popup-blocked-to-post-blank-expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-CONSOLE ERROR: line 15: Not allowed to navigate top frame to data URL: data:text/html,<script>alert(window)</script>?
-If the POST pop-up was not blocked then there will be an ALERT containing a Window object. Otherwise, the test passes.
diff --git a/third_party/inspector_protocol/README.chromium b/third_party/inspector_protocol/README.chromium
index 0b53c5a..3aa6bb47 100644
--- a/third_party/inspector_protocol/README.chromium
+++ b/third_party/inspector_protocol/README.chromium
@@ -2,7 +2,7 @@
 Short Name: inspector_protocol
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: 0
-Revision: 454b6bcf7d08535681bbd967030f5248bcbc8e6a
+Revision: a8dc93daa67c3bfad9c56aa9641ab2f3bc6d3fc0
 License: BSD
 License File: LICENSE
 Security Critical: no
diff --git a/third_party/inspector_protocol/encoding/encoding.cc b/third_party/inspector_protocol/encoding/encoding.cc
index e84ceda..ceda4437 100644
--- a/third_party/inspector_protocol/encoding/encoding.cc
+++ b/third_party/inspector_protocol/encoding/encoding.cc
@@ -80,8 +80,8 @@
 
 // Writes the bytes for |v| to |out|, starting with the most significant byte.
 // See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
-template <typename T>
-void WriteBytesMostSignificantByteFirst(T v, std::vector<uint8_t>* out) {
+template <typename T, class C>
+void WriteBytesMostSignificantByteFirst(T v, C* out) {
   for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes)
     out->push_back(0xff & (v >> (shift_bytes * 8)));
 }
@@ -151,9 +151,8 @@
 
 // Writes the start of a token with |type|. The |value| may indicate the size,
 // or it may be the payload if the value is an unsigned integer.
-void WriteTokenStart(MajorType type,
-                     uint64_t value,
-                     std::vector<uint8_t>* encoded) {
+template <class C>
+void WriteTokenStartTmpl(MajorType type, uint64_t value, C* encoded) {
   if (value < 24) {
     // Values 0-23 are encoded directly into the additional info of the
     // initial byte.
@@ -183,6 +182,14 @@
   encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
   WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
 }
+void WriteTokenStart(MajorType type,
+                     uint64_t value,
+                     std::vector<uint8_t>* encoded) {
+  WriteTokenStartTmpl(type, value, encoded);
+}
+void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) {
+  WriteTokenStartTmpl(type, value, encoded);
+}
 }  // namespace internals
 
 // =============================================================================
@@ -226,7 +233,8 @@
   return kStopByte;
 }
 
-void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeInt32Tmpl(int32_t value, C* out) {
   if (value >= 0) {
     internals::WriteTokenStart(MajorType::UNSIGNED, value, out);
   } else {
@@ -234,8 +242,15 @@
     internals::WriteTokenStart(MajorType::NEGATIVE, representation, out);
   }
 }
+void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
+  EncodeInt32Tmpl(value, out);
+}
+void EncodeInt32(int32_t value, std::string* out) {
+  EncodeInt32Tmpl(value, out);
+}
 
-void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeString16Tmpl(span<uint16_t> in, C* out) {
   uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
   internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
   // When emitting UTF16 characters, we always write the least significant byte
@@ -252,14 +267,28 @@
     out->push_back(two_bytes >> 8);
   }
 }
+void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
+  EncodeString16Tmpl(in, out);
+}
+void EncodeString16(span<uint16_t> in, std::string* out) {
+  EncodeString16Tmpl(in, out);
+}
 
-void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeString8Tmpl(span<uint8_t> in, C* out) {
   internals::WriteTokenStart(MajorType::STRING,
                              static_cast<uint64_t>(in.size_bytes()), out);
   out->insert(out->end(), in.begin(), in.end());
 }
+void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
+  EncodeString8Tmpl(in, out);
+}
+void EncodeString8(span<uint8_t> in, std::string* out) {
+  EncodeString8Tmpl(in, out);
+}
 
-void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
   for (std::ptrdiff_t ii = 0; ii < latin1.size(); ++ii) {
     if (latin1[ii] <= 127)
       continue;
@@ -279,8 +308,15 @@
   }
   EncodeString8(latin1, out);
 }
+void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
+  EncodeFromLatin1Tmpl(latin1, out);
+}
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out) {
+  EncodeFromLatin1Tmpl(latin1, out);
+}
 
-void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeFromUTF16Tmpl(span<uint16_t> utf16, C* out) {
   // If there's at least one non-ASCII char, encode as STRING16 (UTF16).
   for (uint16_t ch : utf16) {
     if (ch <= 127)
@@ -293,13 +329,26 @@
                              static_cast<uint64_t>(utf16.size()), out);
   out->insert(out->end(), utf16.begin(), utf16.end());
 }
+void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
+  EncodeFromUTF16Tmpl(utf16, out);
+}
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out) {
+  EncodeFromUTF16Tmpl(utf16, out);
+}
 
-void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeBinaryTmpl(span<uint8_t> in, C* out) {
   out->push_back(kExpectedConversionToBase64Tag);
   uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
   internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
   out->insert(out->end(), in.begin(), in.end());
 }
+void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
+  EncodeBinaryTmpl(in, out);
+}
+void EncodeBinary(span<uint8_t> in, std::string* out) {
+  EncodeBinaryTmpl(in, out);
+}
 
 // A double is encoded with a specific initial byte
 // (kInitialByteForDouble) plus the 64 bits of payload for its value.
@@ -310,7 +359,8 @@
 // bit wide length, plus a 32 bit length for that string.
 constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
 
-void EncodeDouble(double value, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeDoubleTmpl(double value, C* out) {
   // The additional_info=27 indicates 64 bits for the double follow.
   // See RFC 7049 Section 2.3, Table 1.
   out->push_back(kInitialByteForDouble);
@@ -321,44 +371,68 @@
   reinterpret.from_double = value;
   WriteBytesMostSignificantByteFirst<uint64_t>(reinterpret.to_uint64, out);
 }
+void EncodeDouble(double value, std::vector<uint8_t>* out) {
+  EncodeDoubleTmpl(value, out);
+}
+void EncodeDouble(double value, std::string* out) {
+  EncodeDoubleTmpl(value, out);
+}
 
 // =============================================================================
 // cbor::EnvelopeEncoder - for wrapping submessages
 // =============================================================================
 
-void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
-  assert(byte_size_pos_ == 0);
+template <class C>
+void EncodeStartTmpl(C* out, std::size_t& byte_size_pos) {
+  assert(byte_size_pos == 0);
   out->push_back(kInitialByteForEnvelope);
   out->push_back(kInitialByteFor32BitLengthByteString);
-  byte_size_pos_ = out->size();
+  byte_size_pos = out->size();
   out->resize(out->size() + sizeof(uint32_t));
 }
 
-bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
-  assert(byte_size_pos_ != 0);
+void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
+  EncodeStartTmpl<std::vector<uint8_t>>(out, byte_size_pos_);
+}
+
+void EnvelopeEncoder::EncodeStart(std::string* out) {
+  EncodeStartTmpl<std::string>(out, byte_size_pos_);
+}
+
+template <class C>
+bool EncodeStopTmpl(C* out, std::size_t& byte_size_pos) {
+  assert(byte_size_pos != 0);
   // The byte size is the size of the payload, that is, all the
   // bytes that were written past the byte size position itself.
-  uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t));
+  uint64_t byte_size = out->size() - (byte_size_pos + sizeof(uint32_t));
   // We store exactly 4 bytes, so at most INT32MAX, with most significant
   // byte first.
   if (byte_size > std::numeric_limits<uint32_t>::max())
     return false;
   for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0;
        --shift_bytes) {
-    (*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8));
+    (*out)[byte_size_pos++] = 0xff & (byte_size >> (shift_bytes * 8));
   }
   return true;
 }
 
+bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
+  return EncodeStopTmpl(out, byte_size_pos_);
+}
+
+bool EnvelopeEncoder::EncodeStop(std::string* out) {
+  return EncodeStopTmpl(out, byte_size_pos_);
+}
+
 // =============================================================================
 // cbor::NewCBOREncoder - for encoding from a streaming parser
 // =============================================================================
 
 namespace {
+template <class C>
 class CBOREncoder : public StreamingParserHandler {
  public:
-  CBOREncoder(std::vector<uint8_t>* out, Status* status)
-      : out_(out), status_(status) {
+  CBOREncoder(C* out, Status* status) : out_(out), status_(status) {
     *status_ = Status();
   }
 
@@ -419,7 +493,7 @@
   }
 
  private:
-  std::vector<uint8_t>* out_;
+  C* out_;
   std::vector<EnvelopeEncoder> envelopes_;
   Status* status_;
 };
@@ -428,7 +502,13 @@
 std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
     std::vector<uint8_t>* out,
     Status* status) {
-  return std::unique_ptr<StreamingParserHandler>(new CBOREncoder(out, status));
+  return std::unique_ptr<StreamingParserHandler>(
+      new CBOREncoder<std::vector<uint8_t>>(out, status));
+}
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+                                                       Status* status) {
+  return std::unique_ptr<StreamingParserHandler>(
+      new CBOREncoder<std::string>(out, status));
 }
 
 // =============================================================================
@@ -861,10 +941,11 @@
 
 namespace {
 // Prints |value| to |out| with 4 hex digits, most significant chunk first.
-void PrintHex(uint16_t value, std::string* out) {
+template <class C>
+void PrintHex(uint16_t value, C* out) {
   for (int ii = 3; ii >= 0; --ii) {
     int four_bits = 0xf & (value >> (4 * ii));
-    out->append(1, four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
+    out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
   }
 }
 
@@ -882,6 +963,9 @@
 class State {
  public:
   explicit State(Container container) : container_(container) {}
+  void StartElement(std::vector<uint8_t>* out) {
+    // FIXME!!!
+  }
   void StartElement(std::string* out) {
     assert(container_ != Container::NONE || size_ == 0);
     if (size_ != 0) {
@@ -901,7 +985,8 @@
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz0123456789+/";
 
-void Base64Encode(const span<uint8_t>& in, std::string* out) {
+template <class C>
+void Base64Encode(const span<uint8_t>& in, C* out) {
   // The following three cases are based on the tables in the example
   // section in https://en.wikipedia.org/wiki/Base64. We process three
   // input bytes at a time, emitting 4 output bytes at a time.
@@ -933,9 +1018,10 @@
 }
 
 // Implements a handler for JSON parser events to emit a JSON string.
+template <class C>
 class JSONEncoder : public StreamingParserHandler {
  public:
-  JSONEncoder(const Platform* platform, std::string* out, Status* status)
+  JSONEncoder(const Platform* platform, C* out, Status* status)
       : platform_(platform), out_(out), status_(status) {
     *status_ = Status();
     state_.emplace(Container::NONE);
@@ -947,7 +1033,7 @@
     assert(!state_.empty());
     state_.top().StartElement(out_);
     state_.emplace(Container::MAP);
-    out_->append("{");
+    Emit('{');
   }
 
   void HandleMapEnd() override {
@@ -955,7 +1041,7 @@
       return;
     assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
     state_.pop();
-    out_->append("}");
+    Emit('}');
   }
 
   void HandleArrayBegin() override {
@@ -963,7 +1049,7 @@
       return;
     state_.top().StartElement(out_);
     state_.emplace(Container::ARRAY);
-    out_->append("[");
+    Emit('[');
   }
 
   void HandleArrayEnd() override {
@@ -971,64 +1057,64 @@
       return;
     assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
     state_.pop();
-    out_->append("]");
+    Emit(']');
   }
 
   void HandleString16(span<uint16_t> chars) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     for (const uint16_t ch : chars) {
       if (ch == '"') {
-        out_->append("\\\"");
+        Emit("\\\"");
       } else if (ch == '\\') {
-        out_->append("\\\\");
+        Emit("\\\\");
       } else if (ch == '\b') {
-        out_->append("\\b");
+        Emit("\\b");
       } else if (ch == '\f') {
-        out_->append("\\f");
+        Emit("\\f");
       } else if (ch == '\n') {
-        out_->append("\\n");
+        Emit("\\n");
       } else if (ch == '\r') {
-        out_->append("\\r");
+        Emit("\\r");
       } else if (ch == '\t') {
-        out_->append("\\t");
+        Emit("\\t");
       } else if (ch >= 32 && ch <= 126) {
-        out_->append(1, ch);
+        Emit(ch);
       } else {
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(ch, out_);
       }
     }
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleString8(span<uint8_t> chars) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     for (std::ptrdiff_t ii = 0; ii < chars.size(); ++ii) {
       uint8_t c = chars[ii];
       if (c == '"') {
-        out_->append("\\\"");
+        Emit("\\\"");
       } else if (c == '\\') {
-        out_->append("\\\\");
+        Emit("\\\\");
       } else if (c == '\b') {
-        out_->append("\\b");
+        Emit("\\b");
       } else if (c == '\f') {
-        out_->append("\\f");
+        Emit("\\f");
       } else if (c == '\n') {
-        out_->append("\\n");
+        Emit("\\n");
       } else if (c == '\r') {
-        out_->append("\\r");
+        Emit("\\r");
       } else if (c == '\t') {
-        out_->append("\\t");
+        Emit("\\t");
       } else if (c >= 32 && c <= 126) {
-        out_->append(1, c);
+        Emit(c);
       } else if (c < 32) {
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>(c), out_);
       } else {
         // Inspect the leading byte to figure out how long the utf8
@@ -1079,29 +1165,29 @@
         // using the math described at https://en.wikipedia.org/wiki/UTF-16,
         // for either one or two 16 bit characters.
         if (codepoint < 0xffff) {
-          out_->append("\\u");
+          Emit("\\u");
           PrintHex(static_cast<uint16_t>(codepoint), out_);
           continue;
         }
         codepoint -= 0x10000;
         // high surrogate
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
         // low surrogate
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
       }
     }
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleBinary(span<uint8_t> bytes) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     Base64Encode(bytes, out_);
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleDouble(double value) override {
@@ -1111,7 +1197,7 @@
     // JSON cannot represent NaN or Infinity. So, for compatibility,
     // we behave like the JSON object in web browsers: emit 'null'.
     if (!std::isfinite(value)) {
-      out_->append("null");
+      Emit("null");
       return;
     }
     std::unique_ptr<char[]> str_value = platform_->DToStr(value);
@@ -1123,33 +1209,33 @@
     // we probe for this and emit the leading 0 anyway if necessary.
     const char* chars = str_value.get();
     if (chars[0] == '.') {
-      out_->append("0");
+      Emit('0');
     } else if (chars[0] == '-' && chars[1] == '.') {
-      out_->append("-0");
+      Emit("-0");
       ++chars;
     }
-    out_->append(chars);
+    Emit(chars);
   }
 
   void HandleInt32(int32_t value) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append(std::to_string(value));
+    Emit(std::to_string(value));
   }
 
   void HandleBool(bool value) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append(value ? "true" : "false");
+    Emit(value ? "true" : "false");
   }
 
   void HandleNull() override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("null");
+    Emit("null");
   }
 
   void HandleError(Status error) override {
@@ -1159,18 +1245,33 @@
   }
 
  private:
+  void Emit(char c) { out_->push_back(c); }
+  void Emit(const char* str) {
+    out_->insert(out_->end(), str, str + strlen(str));
+  }
+  void Emit(const std::string& str) {
+    out_->insert(out_->end(), str.begin(), str.end());
+  }
+
   const Platform* platform_;
-  std::string* out_;
+  C* out_;
   Status* status_;
   std::stack<State> state_;
 };
 }  // namespace
 
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+    const Platform* platform,
+    std::vector<uint8_t>* out,
+    Status* status) {
+  return std::unique_ptr<StreamingParserHandler>(
+      new JSONEncoder<std::vector<uint8_t>>(platform, out, status));
+}
 std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
                                                        std::string* out,
                                                        Status* status) {
   return std::unique_ptr<StreamingParserHandler>(
-      new JSONEncoder(platform, out, status));
+      new JSONEncoder<std::string>(platform, out, status));
 }
 
 // =============================================================================
@@ -1786,18 +1887,64 @@
 };
 }  // namespace
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint8_t> chars,
                StreamingParserHandler* handler) {
-  JsonParser<uint8_t> parser(platform, handler);
+  JsonParser<uint8_t> parser(&platform, handler);
   parser.Parse(chars.data(), chars.size());
 }
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint16_t> chars,
                StreamingParserHandler* handler) {
-  JsonParser<uint16_t> parser(platform, handler);
+  JsonParser<uint16_t> parser(&platform, handler);
   parser.Parse(chars.data(), chars.size());
 }
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+template <class C>
+Status ConvertCBORToJSONTmpl(const Platform& platform,
+                             span<uint8_t> cbor,
+                             C* json) {
+  Status status;
+  std::unique_ptr<StreamingParserHandler> json_writer =
+      NewJSONEncoder(&platform, json, &status);
+  cbor::ParseCBOR(cbor, json_writer.get());
+  return status;
+}
+
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::vector<uint8_t>* json) {
+  return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::string* json) {
+  return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+
+template <class C>
+Status ConvertJSONToCBORTmpl(const Platform& platform,
+                             span<uint8_t> json,
+                             C* cbor) {
+  Status status;
+  std::unique_ptr<StreamingParserHandler> encoder =
+      cbor::NewCBOREncoder(cbor, &status);
+  ParseJSON(platform, json, encoder.get());
+  return status;
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::string* cbor) {
+  return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::vector<uint8_t>* cbor) {
+  return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
 }  // namespace json
 }  // namespace inspector_protocol_encoding
diff --git a/third_party/inspector_protocol/encoding/encoding.h b/third_party/inspector_protocol/encoding/encoding.h
index b958ff4..dc6dbfd 100644
--- a/third_party/inspector_protocol/encoding/encoding.h
+++ b/third_party/inspector_protocol/encoding/encoding.h
@@ -189,32 +189,39 @@
 // Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE|
 // (major type 1) iff < 0.
 void EncodeInt32(int32_t value, std::vector<uint8_t>* out);
+void EncodeInt32(int32_t value, std::string* out);
 
 // Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16
 // character in |in| is emitted with most significant byte first,
 // appending to |out|.
 void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out);
+void EncodeString16(span<uint16_t> in, std::string* out);
 
 // Encodes a UTF8 string |in| as STRING (major type 3).
 void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeString8(span<uint8_t> in, std::string* out);
 
 // Encodes the given |latin1| string as STRING8.
 // If any non-ASCII character is present, it will be represented
 // as a 2 byte UTF8 sequence.
 void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out);
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out);
 
 // Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII.
 // Otherwise, encodes as STRING16.
 void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out);
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out);
 
 // Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with
 // definitive length, prefixed with tag 22 indicating expected conversion to
 // base64 (see RFC 7049, Table 3 and Section 2.4.4.2).
 void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeBinary(span<uint8_t> in, std::string* out);
 
 // Encodes / decodes a double as Major type 7 (SIMPLE_VALUE),
 // with additional info = 27, followed by 8 bytes in big endian.
 void EncodeDouble(double value, std::vector<uint8_t>* out);
+void EncodeDouble(double value, std::string* out);
 
 // =============================================================================
 // cbor::EnvelopeEncoder - for wrapping submessages
@@ -233,9 +240,11 @@
   // byte size in |byte_size_pos_|. Also emits empty bytes for the
   // byte sisze so that encoding can continue.
   void EncodeStart(std::vector<uint8_t>* out);
+  void EncodeStart(std::string* out);
   // This records the current size in |out| at position byte_size_pos_.
   // Returns true iff successful.
   bool EncodeStop(std::vector<uint8_t>* out);
+  bool EncodeStop(std::string* out);
 
  private:
   std::size_t byte_size_pos_ = 0;
@@ -252,6 +261,8 @@
 std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
     std::vector<uint8_t>* out,
     Status* status);
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+                                                       Status* status);
 
 // =============================================================================
 // cbor::CBORTokenizer - for parsing individual CBOR items
@@ -389,6 +400,9 @@
 void WriteTokenStart(cbor::MajorType type,
                      uint64_t value,
                      std::vector<uint8_t>* encoded);
+void WriteTokenStart(cbor::MajorType type,
+                     uint64_t value,
+                     std::string* encoded);
 }  // namespace internals
 }  // namespace cbor
 
@@ -416,6 +430,10 @@
 // Except for calling the HandleError routine at any time, the client
 // code must call the Handle* methods in an order in which they'd occur
 // in valid JSON; otherwise we may crash (the code uses assert).
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+    const Platform* platform,
+    std::vector<uint8_t>* out,
+    Status* status);
 std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
                                                        std::string* out,
                                                        Status* status);
@@ -424,12 +442,28 @@
 // json::ParseJSON - for receiving streaming parser events for JSON
 // =============================================================================
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint8_t> chars,
                StreamingParserHandler* handler);
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint16_t> chars,
                StreamingParserHandler* handler);
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::string* json);
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::vector<uint8_t>* json);
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::vector<uint8_t>* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::string* cbor);
 }  // namespace json
 }  // namespace inspector_protocol_encoding
 
diff --git a/third_party/inspector_protocol/encoding/encoding_test.cc b/third_party/inspector_protocol/encoding/encoding_test.cc
index e50ab58..baf7868 100644
--- a/third_party/inspector_protocol/encoding/encoding_test.cc
+++ b/third_party/inspector_protocol/encoding/encoding_test.cc
@@ -68,9 +68,9 @@
   }
 };
 
-json::Platform* GetTestPlatform() {
+const json::Platform& GetTestPlatform() {
   static TestPlatform* platform = new TestPlatform;
-  return platform;
+  return *platform;
 }
 
 // =============================================================================
@@ -670,7 +670,7 @@
   // And now we roundtrip, decoding the message we just encoded.
   std::string decoded;
   std::unique_ptr<StreamingParserHandler> json_encoder =
-      NewJSONEncoder(GetTestPlatform(), &decoded, &status);
+      NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
   ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_encoder.get());
   EXPECT_EQ(Error::OK, status.error);
   EXPECT_EQ(json, decoded);
@@ -691,7 +691,7 @@
     ParseJSON(GetTestPlatform(), ascii_in, encoder.get());
     std::string decoded;
     std::unique_ptr<StreamingParserHandler> json_writer =
-        NewJSONEncoder(GetTestPlatform(), &decoded, &status);
+        NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
     ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_writer.get());
     EXPECT_EQ(Error::OK, status.error);
     EXPECT_EQ(json, decoded);
@@ -724,7 +724,7 @@
   // Now drive the json writer via the CBOR decoder.
   std::string decoded;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &decoded, &status);
+      NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
   ParseCBOR(SpanFromVector(encoded), json_writer.get());
   EXPECT_EQ(Error::OK, status.error);
   EXPECT_EQ(Status::npos(), status.pos);
@@ -744,7 +744,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
   EXPECT_EQ(Error::OK, status.error);
   EXPECT_EQ("{}", out);
@@ -768,7 +768,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::OK, status.error);
   EXPECT_EQ("{\"msg\":\"Hello, \\ud83c\\udf0e.\"}", out);
@@ -793,7 +793,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::OK, status.error);
   EXPECT_EQ("{\"\\ud83c\\udf0e\":\"\\u263e\"}", out);
@@ -804,7 +804,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_NO_INPUT, status.error);
   EXPECT_EQ("", out);
@@ -818,7 +818,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(SpanFromStdString(json), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_START_BYTE, status.error);
   EXPECT_EQ("", out);
@@ -834,7 +834,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, status.error);
   EXPECT_EQ(static_cast<int64_t>(bytes.size()), status.pos);
@@ -852,7 +852,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, status.error);
   EXPECT_EQ(static_cast<int64_t>(bytes.size()), status.pos);
@@ -867,7 +867,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_MAP, status.error);
   EXPECT_EQ(7, status.pos);
@@ -884,7 +884,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_MAP_KEY, status.error);
   EXPECT_EQ(7, status.pos);
@@ -915,7 +915,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> json_writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
     EXPECT_EQ(Error::OK, status.error);
     EXPECT_EQ(Status::npos(), status.pos);
@@ -926,7 +926,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> json_writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
     EXPECT_EQ(Error::OK, status.error);
     EXPECT_EQ(Status::npos(), status.pos);
@@ -946,7 +946,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> json_writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
     EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
     EXPECT_EQ(opening_segment_size * 301, status.pos);
@@ -956,7 +956,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> json_writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
     EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
     EXPECT_EQ(opening_segment_size * 301, status.pos);
@@ -975,7 +975,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_UNSUPPORTED_VALUE, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -998,7 +998,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_STRING16, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1018,7 +1018,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_STRING8, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1040,7 +1040,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_BINARY, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1061,7 +1061,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_DOUBLE, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1082,7 +1082,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_INVALID_INT32, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1105,7 +1105,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> json_writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
   EXPECT_EQ(Error::CBOR_TRAILING_JUNK, status.error);
   EXPECT_EQ(error_pos, status.pos);
@@ -1127,7 +1127,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   writer->HandleMapBegin();
   WriteUTF8AsUTF16(writer.get(), "msg1");
   WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
@@ -1171,7 +1171,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   writer->HandleMapBegin();
   writer->HandleString8(SpanFromStdString("Infinity"));
   writer->HandleDouble(std::numeric_limits<double>::infinity());
@@ -1192,7 +1192,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     writer->HandleBinary(SpanFromVector(std::vector<uint8_t>({'M', 'a', 'n'})));
     EXPECT_TRUE(status.ok());
     EXPECT_EQ("\"TWFu\"", out);
@@ -1201,7 +1201,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     writer->HandleBinary(SpanFromVector(std::vector<uint8_t>({'M', 'a'})));
     EXPECT_TRUE(status.ok());
     EXPECT_EQ("\"TWE=\"", out);
@@ -1210,7 +1210,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     writer->HandleBinary(SpanFromVector(std::vector<uint8_t>({'M'})));
     EXPECT_TRUE(status.ok());
     EXPECT_EQ("\"TQ==\"", out);
@@ -1219,7 +1219,7 @@
     std::string out;
     Status status;
     std::unique_ptr<StreamingParserHandler> writer =
-        NewJSONEncoder(GetTestPlatform(), &out, &status);
+        NewJSONEncoder(&GetTestPlatform(), &out, &status);
     writer->HandleBinary(SpanFromVector(std::vector<uint8_t>(
         {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
     EXPECT_TRUE(status.ok());
@@ -1233,7 +1233,7 @@
   std::string out;
   Status status;
   std::unique_ptr<StreamingParserHandler> writer =
-      NewJSONEncoder(GetTestPlatform(), &out, &status);
+      NewJSONEncoder(&GetTestPlatform(), &out, &status);
   writer->HandleMapBegin();
   WriteUTF8AsUTF16(writer.get(), "msg1");
   writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
@@ -1610,5 +1610,24 @@
   EXPECT_EQ(0, log_.status().pos);
   EXPECT_EQ("", log_.str());
 }
+
+TEST(ConvertJSONToCBOR, RoundTripValidJson) {
+  std::string json = "{\"msg\":\"Hello, world.\"}";
+  std::string cbor;
+  {
+    Status status =
+        ConvertJSONToCBOR(GetTestPlatform(), SpanFromStdString(json), &cbor);
+    EXPECT_EQ(Error::OK, status.error);
+    EXPECT_EQ(Status::npos(), status.pos);
+  }
+  std::string roundtrip_json;
+  {
+    Status status = ConvertCBORToJSON(GetTestPlatform(),
+                                      SpanFromStdString(cbor), &roundtrip_json);
+    EXPECT_EQ(Error::OK, status.error);
+    EXPECT_EQ(Status::npos(), status.pos);
+  }
+  EXPECT_EQ(json, roundtrip_json);
+}
 }  // namespace json
 }  // namespace inspector_protocol_encoding
diff --git a/third_party/inspector_protocol/lib/encoding_cpp.template b/third_party/inspector_protocol/lib/encoding_cpp.template
index 84251d9..7127409 100644
--- a/third_party/inspector_protocol/lib/encoding_cpp.template
+++ b/third_party/inspector_protocol/lib/encoding_cpp.template
@@ -7,6 +7,7 @@
 
 
 #include <cassert>
+#include <cmath>
 #include <cstring>
 #include <limits>
 #include <stack>
@@ -86,8 +87,8 @@
 
 // Writes the bytes for |v| to |out|, starting with the most significant byte.
 // See also: https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
-template <typename T>
-void WriteBytesMostSignificantByteFirst(T v, std::vector<uint8_t>* out) {
+template <typename T, class C>
+void WriteBytesMostSignificantByteFirst(T v, C* out) {
   for (int shift_bytes = sizeof(T) - 1; shift_bytes >= 0; --shift_bytes)
     out->push_back(0xff & (v >> (shift_bytes * 8)));
 }
@@ -157,9 +158,8 @@
 
 // Writes the start of a token with |type|. The |value| may indicate the size,
 // or it may be the payload if the value is an unsigned integer.
-void WriteTokenStart(MajorType type,
-                     uint64_t value,
-                     std::vector<uint8_t>* encoded) {
+template <class C>
+void WriteTokenStartTmpl(MajorType type, uint64_t value, C* encoded) {
   if (value < 24) {
     // Values 0-23 are encoded directly into the additional info of the
     // initial byte.
@@ -189,6 +189,14 @@
   encoded->push_back(EncodeInitialByte(type, kAdditionalInformation8Bytes));
   WriteBytesMostSignificantByteFirst<uint64_t>(value, encoded);
 }
+void WriteTokenStart(MajorType type,
+                     uint64_t value,
+                     std::vector<uint8_t>* encoded) {
+  WriteTokenStartTmpl(type, value, encoded);
+}
+void WriteTokenStart(MajorType type, uint64_t value, std::string* encoded) {
+  WriteTokenStartTmpl(type, value, encoded);
+}
 }  // namespace internals
 
 // =============================================================================
@@ -232,7 +240,8 @@
   return kStopByte;
 }
 
-void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeInt32Tmpl(int32_t value, C* out) {
   if (value >= 0) {
     internals::WriteTokenStart(MajorType::UNSIGNED, value, out);
   } else {
@@ -240,8 +249,15 @@
     internals::WriteTokenStart(MajorType::NEGATIVE, representation, out);
   }
 }
+void EncodeInt32(int32_t value, std::vector<uint8_t>* out) {
+  EncodeInt32Tmpl(value, out);
+}
+void EncodeInt32(int32_t value, std::string* out) {
+  EncodeInt32Tmpl(value, out);
+}
 
-void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeString16Tmpl(span<uint16_t> in, C* out) {
   uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
   internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
   // When emitting UTF16 characters, we always write the least significant byte
@@ -258,14 +274,28 @@
     out->push_back(two_bytes >> 8);
   }
 }
+void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out) {
+  EncodeString16Tmpl(in, out);
+}
+void EncodeString16(span<uint16_t> in, std::string* out) {
+  EncodeString16Tmpl(in, out);
+}
 
-void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeString8Tmpl(span<uint8_t> in, C* out) {
   internals::WriteTokenStart(MajorType::STRING,
                              static_cast<uint64_t>(in.size_bytes()), out);
   out->insert(out->end(), in.begin(), in.end());
 }
+void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out) {
+  EncodeString8Tmpl(in, out);
+}
+void EncodeString8(span<uint8_t> in, std::string* out) {
+  EncodeString8Tmpl(in, out);
+}
 
-void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeFromLatin1Tmpl(span<uint8_t> latin1, C* out) {
   for (std::ptrdiff_t ii = 0; ii < latin1.size(); ++ii) {
     if (latin1[ii] <= 127)
       continue;
@@ -285,8 +315,15 @@
   }
   EncodeString8(latin1, out);
 }
+void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out) {
+  EncodeFromLatin1Tmpl(latin1, out);
+}
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out) {
+  EncodeFromLatin1Tmpl(latin1, out);
+}
 
-void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeFromUTF16Tmpl(span<uint16_t> utf16, C* out) {
   // If there's at least one non-ASCII char, encode as STRING16 (UTF16).
   for (uint16_t ch : utf16) {
     if (ch <= 127)
@@ -299,13 +336,26 @@
                              static_cast<uint64_t>(utf16.size()), out);
   out->insert(out->end(), utf16.begin(), utf16.end());
 }
+void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out) {
+  EncodeFromUTF16Tmpl(utf16, out);
+}
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out) {
+  EncodeFromUTF16Tmpl(utf16, out);
+}
 
-void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeBinaryTmpl(span<uint8_t> in, C* out) {
   out->push_back(kExpectedConversionToBase64Tag);
   uint64_t byte_length = static_cast<uint64_t>(in.size_bytes());
   internals::WriteTokenStart(MajorType::BYTE_STRING, byte_length, out);
   out->insert(out->end(), in.begin(), in.end());
 }
+void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
+  EncodeBinaryTmpl(in, out);
+}
+void EncodeBinary(span<uint8_t> in, std::string* out) {
+  EncodeBinaryTmpl(in, out);
+}
 
 // A double is encoded with a specific initial byte
 // (kInitialByteForDouble) plus the 64 bits of payload for its value.
@@ -316,7 +366,8 @@
 // bit wide length, plus a 32 bit length for that string.
 constexpr std::ptrdiff_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
 
-void EncodeDouble(double value, std::vector<uint8_t>* out) {
+template <class C>
+void EncodeDoubleTmpl(double value, C* out) {
   // The additional_info=27 indicates 64 bits for the double follow.
   // See RFC 7049 Section 2.3, Table 1.
   out->push_back(kInitialByteForDouble);
@@ -327,44 +378,68 @@
   reinterpret.from_double = value;
   WriteBytesMostSignificantByteFirst<uint64_t>(reinterpret.to_uint64, out);
 }
+void EncodeDouble(double value, std::vector<uint8_t>* out) {
+  EncodeDoubleTmpl(value, out);
+}
+void EncodeDouble(double value, std::string* out) {
+  EncodeDoubleTmpl(value, out);
+}
 
 // =============================================================================
 // cbor::EnvelopeEncoder - for wrapping submessages
 // =============================================================================
 
-void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
-  assert(byte_size_pos_ == 0);
+template <class C>
+void EncodeStartTmpl(C* out, std::size_t& byte_size_pos) {
+  assert(byte_size_pos == 0);
   out->push_back(kInitialByteForEnvelope);
   out->push_back(kInitialByteFor32BitLengthByteString);
-  byte_size_pos_ = out->size();
+  byte_size_pos = out->size();
   out->resize(out->size() + sizeof(uint32_t));
 }
 
-bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
-  assert(byte_size_pos_ != 0);
+void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
+  EncodeStartTmpl<std::vector<uint8_t>>(out, byte_size_pos_);
+}
+
+void EnvelopeEncoder::EncodeStart(std::string* out) {
+  EncodeStartTmpl<std::string>(out, byte_size_pos_);
+}
+
+template <class C>
+bool EncodeStopTmpl(C* out, std::size_t& byte_size_pos) {
+  assert(byte_size_pos != 0);
   // The byte size is the size of the payload, that is, all the
   // bytes that were written past the byte size position itself.
-  uint64_t byte_size = out->size() - (byte_size_pos_ + sizeof(uint32_t));
+  uint64_t byte_size = out->size() - (byte_size_pos + sizeof(uint32_t));
   // We store exactly 4 bytes, so at most INT32MAX, with most significant
   // byte first.
   if (byte_size > std::numeric_limits<uint32_t>::max())
     return false;
   for (int shift_bytes = sizeof(uint32_t) - 1; shift_bytes >= 0;
        --shift_bytes) {
-    (*out)[byte_size_pos_++] = 0xff & (byte_size >> (shift_bytes * 8));
+    (*out)[byte_size_pos++] = 0xff & (byte_size >> (shift_bytes * 8));
   }
   return true;
 }
 
+bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
+  return EncodeStopTmpl(out, byte_size_pos_);
+}
+
+bool EnvelopeEncoder::EncodeStop(std::string* out) {
+  return EncodeStopTmpl(out, byte_size_pos_);
+}
+
 // =============================================================================
 // cbor::NewCBOREncoder - for encoding from a streaming parser
 // =============================================================================
 
 namespace {
+template <class C>
 class CBOREncoder : public StreamingParserHandler {
  public:
-  CBOREncoder(std::vector<uint8_t>* out, Status* status)
-      : out_(out), status_(status) {
+  CBOREncoder(C* out, Status* status) : out_(out), status_(status) {
     *status_ = Status();
   }
 
@@ -425,7 +500,7 @@
   }
 
  private:
-  std::vector<uint8_t>* out_;
+  C* out_;
   std::vector<EnvelopeEncoder> envelopes_;
   Status* status_;
 };
@@ -434,7 +509,13 @@
 std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
     std::vector<uint8_t>* out,
     Status* status) {
-  return std::unique_ptr<StreamingParserHandler>(new CBOREncoder(out, status));
+  return std::unique_ptr<StreamingParserHandler>(
+      new CBOREncoder<std::vector<uint8_t>>(out, status));
+}
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+                                                       Status* status) {
+  return std::unique_ptr<StreamingParserHandler>(
+      new CBOREncoder<std::string>(out, status));
 }
 
 // =============================================================================
@@ -666,7 +747,7 @@
 namespace {
 // When parsing CBOR, we limit recursion depth for objects and arrays
 // to this constant.
-static constexpr int kStackLimit = 1000;
+static constexpr int kStackLimit = 300;
 
 // Below are three parsing routines for CBOR, which cover enough
 // to roundtrip JSON messages.
@@ -867,10 +948,11 @@
 
 namespace {
 // Prints |value| to |out| with 4 hex digits, most significant chunk first.
-void PrintHex(uint16_t value, std::string* out) {
+template <class C>
+void PrintHex(uint16_t value, C* out) {
   for (int ii = 3; ii >= 0; --ii) {
     int four_bits = 0xf & (value >> (4 * ii));
-    out->append(1, four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
+    out->push_back(four_bits + ((four_bits <= 9) ? '0' : ('a' - 10)));
   }
 }
 
@@ -888,6 +970,9 @@
 class State {
  public:
   explicit State(Container container) : container_(container) {}
+  void StartElement(std::vector<uint8_t>* out) {
+    // FIXME!!!
+  }
   void StartElement(std::string* out) {
     assert(container_ != Container::NONE || size_ == 0);
     if (size_ != 0) {
@@ -907,7 +992,8 @@
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz0123456789+/";
 
-void Base64Encode(const span<uint8_t>& in, std::string* out) {
+template <class C>
+void Base64Encode(const span<uint8_t>& in, C* out) {
   // The following three cases are based on the tables in the example
   // section in https://en.wikipedia.org/wiki/Base64. We process three
   // input bytes at a time, emitting 4 output bytes at a time.
@@ -939,9 +1025,10 @@
 }
 
 // Implements a handler for JSON parser events to emit a JSON string.
+template <class C>
 class JSONEncoder : public StreamingParserHandler {
  public:
-  JSONEncoder(const Platform* platform, std::string* out, Status* status)
+  JSONEncoder(const Platform* platform, C* out, Status* status)
       : platform_(platform), out_(out), status_(status) {
     *status_ = Status();
     state_.emplace(Container::NONE);
@@ -953,7 +1040,7 @@
     assert(!state_.empty());
     state_.top().StartElement(out_);
     state_.emplace(Container::MAP);
-    out_->append("{");
+    Emit('{');
   }
 
   void HandleMapEnd() override {
@@ -961,7 +1048,7 @@
       return;
     assert(state_.size() >= 2 && state_.top().container() == Container::MAP);
     state_.pop();
-    out_->append("}");
+    Emit('}');
   }
 
   void HandleArrayBegin() override {
@@ -969,7 +1056,7 @@
       return;
     state_.top().StartElement(out_);
     state_.emplace(Container::ARRAY);
-    out_->append("[");
+    Emit('[');
   }
 
   void HandleArrayEnd() override {
@@ -977,64 +1064,64 @@
       return;
     assert(state_.size() >= 2 && state_.top().container() == Container::ARRAY);
     state_.pop();
-    out_->append("]");
+    Emit(']');
   }
 
   void HandleString16(span<uint16_t> chars) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     for (const uint16_t ch : chars) {
       if (ch == '"') {
-        out_->append("\\\"");
+        Emit("\\\"");
       } else if (ch == '\\') {
-        out_->append("\\\\");
+        Emit("\\\\");
       } else if (ch == '\b') {
-        out_->append("\\b");
+        Emit("\\b");
       } else if (ch == '\f') {
-        out_->append("\\f");
+        Emit("\\f");
       } else if (ch == '\n') {
-        out_->append("\\n");
+        Emit("\\n");
       } else if (ch == '\r') {
-        out_->append("\\r");
+        Emit("\\r");
       } else if (ch == '\t') {
-        out_->append("\\t");
+        Emit("\\t");
       } else if (ch >= 32 && ch <= 126) {
-        out_->append(1, ch);
+        Emit(ch);
       } else {
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(ch, out_);
       }
     }
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleString8(span<uint8_t> chars) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     for (std::ptrdiff_t ii = 0; ii < chars.size(); ++ii) {
       uint8_t c = chars[ii];
       if (c == '"') {
-        out_->append("\\\"");
+        Emit("\\\"");
       } else if (c == '\\') {
-        out_->append("\\\\");
+        Emit("\\\\");
       } else if (c == '\b') {
-        out_->append("\\b");
+        Emit("\\b");
       } else if (c == '\f') {
-        out_->append("\\f");
+        Emit("\\f");
       } else if (c == '\n') {
-        out_->append("\\n");
+        Emit("\\n");
       } else if (c == '\r') {
-        out_->append("\\r");
+        Emit("\\r");
       } else if (c == '\t') {
-        out_->append("\\t");
+        Emit("\\t");
       } else if (c >= 32 && c <= 126) {
-        out_->append(1, c);
+        Emit(c);
       } else if (c < 32) {
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>(c), out_);
       } else {
         // Inspect the leading byte to figure out how long the utf8
@@ -1085,35 +1172,41 @@
         // using the math described at https://en.wikipedia.org/wiki/UTF-16,
         // for either one or two 16 bit characters.
         if (codepoint < 0xffff) {
-          out_->append("\\u");
+          Emit("\\u");
           PrintHex(static_cast<uint16_t>(codepoint), out_);
           continue;
         }
         codepoint -= 0x10000;
         // high surrogate
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>((codepoint >> 10) + 0xd800), out_);
         // low surrogate
-        out_->append("\\u");
+        Emit("\\u");
         PrintHex(static_cast<uint16_t>((codepoint & 0x3ff) + 0xdc00), out_);
       }
     }
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleBinary(span<uint8_t> bytes) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("\"");
+    Emit('"');
     Base64Encode(bytes, out_);
-    out_->append("\"");
+    Emit('"');
   }
 
   void HandleDouble(double value) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
+    // JSON cannot represent NaN or Infinity. So, for compatibility,
+    // we behave like the JSON object in web browsers: emit 'null'.
+    if (!std::isfinite(value)) {
+      Emit("null");
+      return;
+    }
     std::unique_ptr<char[]> str_value = platform_->DToStr(value);
 
     // DToStr may fail to emit a 0 before the decimal dot. E.g. this is
@@ -1123,33 +1216,33 @@
     // we probe for this and emit the leading 0 anyway if necessary.
     const char* chars = str_value.get();
     if (chars[0] == '.') {
-      out_->append("0");
+      Emit('0');
     } else if (chars[0] == '-' && chars[1] == '.') {
-      out_->append("-0");
+      Emit("-0");
       ++chars;
     }
-    out_->append(chars);
+    Emit(chars);
   }
 
   void HandleInt32(int32_t value) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append(std::to_string(value));
+    Emit(std::to_string(value));
   }
 
   void HandleBool(bool value) override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append(value ? "true" : "false");
+    Emit(value ? "true" : "false");
   }
 
   void HandleNull() override {
     if (!status_->ok())
       return;
     state_.top().StartElement(out_);
-    out_->append("null");
+    Emit("null");
   }
 
   void HandleError(Status error) override {
@@ -1159,18 +1252,33 @@
   }
 
  private:
+  void Emit(char c) { out_->push_back(c); }
+  void Emit(const char* str) {
+    out_->insert(out_->end(), str, str + strlen(str));
+  }
+  void Emit(const std::string& str) {
+    out_->insert(out_->end(), str.begin(), str.end());
+  }
+
   const Platform* platform_;
-  std::string* out_;
+  C* out_;
   Status* status_;
   std::stack<State> state_;
 };
 }  // namespace
 
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+    const Platform* platform,
+    std::vector<uint8_t>* out,
+    Status* status) {
+  return std::unique_ptr<StreamingParserHandler>(
+      new JSONEncoder<std::vector<uint8_t>>(platform, out, status));
+}
 std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
                                                        std::string* out,
                                                        Status* status) {
   return std::unique_ptr<StreamingParserHandler>(
-      new JSONEncoder(platform, out, status));
+      new JSONEncoder<std::string>(platform, out, status));
 }
 
 // =============================================================================
@@ -1178,7 +1286,7 @@
 // =============================================================================
 
 namespace {
-const int kStackLimit = 1000;
+const int kStackLimit = 300;
 
 enum Token {
   ObjectBegin,
@@ -1786,19 +1894,65 @@
 };
 }  // namespace
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint8_t> chars,
                StreamingParserHandler* handler) {
-  JsonParser<uint8_t> parser(platform, handler);
+  JsonParser<uint8_t> parser(&platform, handler);
   parser.Parse(chars.data(), chars.size());
 }
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint16_t> chars,
                StreamingParserHandler* handler) {
-  JsonParser<uint16_t> parser(platform, handler);
+  JsonParser<uint16_t> parser(&platform, handler);
   parser.Parse(chars.data(), chars.size());
 }
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+template <class C>
+Status ConvertCBORToJSONTmpl(const Platform& platform,
+                             span<uint8_t> cbor,
+                             C* json) {
+  Status status;
+  std::unique_ptr<StreamingParserHandler> json_writer =
+      NewJSONEncoder(&platform, json, &status);
+  cbor::ParseCBOR(cbor, json_writer.get());
+  return status;
+}
+
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::vector<uint8_t>* json) {
+  return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::string* json) {
+  return ConvertCBORToJSONTmpl(platform, cbor, json);
+}
+
+template <class C>
+Status ConvertJSONToCBORTmpl(const Platform& platform,
+                             span<uint8_t> json,
+                             C* cbor) {
+  Status status;
+  std::unique_ptr<StreamingParserHandler> encoder =
+      cbor::NewCBOREncoder(cbor, &status);
+  ParseJSON(platform, json, encoder.get());
+  return status;
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::string* cbor) {
+  return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::vector<uint8_t>* cbor) {
+  return ConvertJSONToCBORTmpl(platform, json, cbor);
+}
 }  // namespace json
 
 {% for namespace in config.protocol.namespace %}
diff --git a/third_party/inspector_protocol/lib/encoding_h.template b/third_party/inspector_protocol/lib/encoding_h.template
index aba28c8..5250a89 100644
--- a/third_party/inspector_protocol/lib/encoding_h.template
+++ b/third_party/inspector_protocol/lib/encoding_h.template
@@ -197,32 +197,39 @@
 // Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE|
 // (major type 1) iff < 0.
 void EncodeInt32(int32_t value, std::vector<uint8_t>* out);
+void EncodeInt32(int32_t value, std::string* out);
 
 // Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16
 // character in |in| is emitted with most significant byte first,
 // appending to |out|.
 void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out);
+void EncodeString16(span<uint16_t> in, std::string* out);
 
 // Encodes a UTF8 string |in| as STRING (major type 3).
 void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeString8(span<uint8_t> in, std::string* out);
 
 // Encodes the given |latin1| string as STRING8.
 // If any non-ASCII character is present, it will be represented
 // as a 2 byte UTF8 sequence.
 void EncodeFromLatin1(span<uint8_t> latin1, std::vector<uint8_t>* out);
+void EncodeFromLatin1(span<uint8_t> latin1, std::string* out);
 
 // Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII.
 // Otherwise, encodes as STRING16.
 void EncodeFromUTF16(span<uint16_t> utf16, std::vector<uint8_t>* out);
+void EncodeFromUTF16(span<uint16_t> utf16, std::string* out);
 
 // Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with
 // definitive length, prefixed with tag 22 indicating expected conversion to
 // base64 (see RFC 7049, Table 3 and Section 2.4.4.2).
 void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out);
+void EncodeBinary(span<uint8_t> in, std::string* out);
 
 // Encodes / decodes a double as Major type 7 (SIMPLE_VALUE),
 // with additional info = 27, followed by 8 bytes in big endian.
 void EncodeDouble(double value, std::vector<uint8_t>* out);
+void EncodeDouble(double value, std::string* out);
 
 // =============================================================================
 // cbor::EnvelopeEncoder - for wrapping submessages
@@ -241,9 +248,11 @@
   // byte size in |byte_size_pos_|. Also emits empty bytes for the
   // byte sisze so that encoding can continue.
   void EncodeStart(std::vector<uint8_t>* out);
+  void EncodeStart(std::string* out);
   // This records the current size in |out| at position byte_size_pos_.
   // Returns true iff successful.
   bool EncodeStop(std::vector<uint8_t>* out);
+  bool EncodeStop(std::string* out);
 
  private:
   std::size_t byte_size_pos_ = 0;
@@ -260,6 +269,8 @@
 std::unique_ptr<StreamingParserHandler> NewCBOREncoder(
     std::vector<uint8_t>* out,
     Status* status);
+std::unique_ptr<StreamingParserHandler> NewCBOREncoder(std::string* out,
+                                                       Status* status);
 
 // =============================================================================
 // cbor::CBORTokenizer - for parsing individual CBOR items
@@ -397,6 +408,9 @@
 void WriteTokenStart(cbor::MajorType type,
                      uint64_t value,
                      std::vector<uint8_t>* encoded);
+void WriteTokenStart(cbor::MajorType type,
+                     uint64_t value,
+                     std::string* encoded);
 }  // namespace internals
 }  // namespace cbor
 
@@ -424,6 +438,10 @@
 // Except for calling the HandleError routine at any time, the client
 // code must call the Handle* methods in an order in which they'd occur
 // in valid JSON; otherwise we may crash (the code uses assert).
+std::unique_ptr<StreamingParserHandler> NewJSONEncoder(
+    const Platform* platform,
+    std::vector<uint8_t>* out,
+    Status* status);
 std::unique_ptr<StreamingParserHandler> NewJSONEncoder(const Platform* platform,
                                                        std::string* out,
                                                        Status* status);
@@ -432,12 +450,28 @@
 // json::ParseJSON - for receiving streaming parser events for JSON
 // =============================================================================
 
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint8_t> chars,
                StreamingParserHandler* handler);
-void ParseJSON(const Platform* platform,
+void ParseJSON(const Platform& platform,
                span<uint16_t> chars,
                StreamingParserHandler* handler);
+
+// =============================================================================
+// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
+// =============================================================================
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::string* json);
+Status ConvertCBORToJSON(const Platform& platform,
+                         span<uint8_t> cbor,
+                         std::vector<uint8_t>* json);
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::vector<uint8_t>* cbor);
+Status ConvertJSONToCBOR(const Platform& platform,
+                         span<uint8_t> json,
+                         std::string* cbor);
 }  // namespace json
 
 {% for namespace in config.protocol.namespace %}
diff --git a/third_party/r8/README.chromium b/third_party/r8/README.chromium
index 209d12b..1a7e0df 100644
--- a/third_party/r8/README.chromium
+++ b/third_party/r8/README.chromium
@@ -1,7 +1,7 @@
 Name: R8
 URL: https://r8.googlesource.com/r8
 Revision: 03ab76ff37d7995f76b639dec680cd49ff6fbc41
-Version: 1.4.14-dev
+Version: 1.5.18-dev
 License: BSD 3-Clause
 License File: NOT_SHIPPED
 Security Critical: no
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f727156..204bd83 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -22805,6 +22805,8 @@
   <int value="2855" label="InputTypeReset"/>
   <int value="2856" label="SelectElementSingle"/>
   <int value="2857" label="SelectElementMultiple"/>
+  <int value="2858" label="V8Animation_Effect_AttributeGetter"/>
+  <int value="2859" label="V8Animation_Effect_AttributeSetter"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -32396,6 +32398,7 @@
   <int value="-1691281364" label="enable-notification-action-icons"/>
   <int value="-1687406612" label="UseSkiaRenderer:disabled"/>
   <int value="-1686782572" label="ChromeHomeInactivitySheetExpansion:disabled"/>
+  <int value="-1684773837" label="TabEngagementReportingAndroid:disabled"/>
   <int value="-1684123448" label="disable-best-effort-tasks"/>
   <int value="-1682843294" label="DataReductionProxyDecidesTransform:enabled"/>
   <int value="-1677715989" label="UnifiedConsent:disabled"/>
@@ -33021,6 +33024,7 @@
   <int value="-747463111" label="ContentSuggestionsNotifications:disabled"/>
   <int value="-746328467" label="ExpensiveBackgroundTimerThrottling:disabled"/>
   <int value="-744159181" label="disable-spdy-proxy-dev-auth-origin"/>
+  <int value="-743590125" label="TabEngagementReportingAndroid:enabled"/>
   <int value="-743103250" label="enable-linkable-ephemeral-apps"/>
   <int value="-742469530" label="DriveFS:disabled"/>
   <int value="-741806604" label="DownloadsUi:disabled"/>
@@ -33059,6 +33063,7 @@
       label="AutofillSaveCardDialogUnlabeledExpirationDate:disabled"/>
   <int value="-697751423" label="disable-quickoffice-component-app"/>
   <int value="-696693295" label="Canvas2DImageChromium:disabled"/>
+  <int value="-694622753" label="VizHitTest:disabled"/>
   <int value="-694187898" label="MashOopViz:disabled"/>
   <int value="-684900739" label="disable-merge-key-char-events"/>
   <int value="-684223908" label="enable-android-wallpapers-app"/>
@@ -33164,6 +33169,7 @@
   <int value="-508250572" label="ServiceWorkerLongRunningMessage:disabled"/>
   <int value="-508143738" label="disable-accelerated-fixed-root-background"/>
   <int value="-506706655" label="respect-autocomplete-off-autofill"/>
+  <int value="-506366023" label="VizHitTest:enabled"/>
   <int value="-505679399" label="FontCacheScaling:enabled"/>
   <int value="-503430431" label="XRSandbox:enabled"/>
   -
@@ -34158,7 +34164,6 @@
   <int value="1108663108" label="disable-device-discovery-notifications"/>
   <int value="1111871757" label="ForceUnifiedConsentBump:enabled"/>
   <int value="1112051724" label="DetectingHeavyPages:enabled"/>
-  <int value="1112885300" label="VizHitTestDrawQuad:disabled"/>
   <int value="1113196543" label="ShowManagedUi:disabled"/>
   <int value="1113365156" label="tab-management-experiment-type-chive"/>
   <int value="1114629582" label="enable-floating-virtual-keyboard"/>
@@ -34383,7 +34388,6 @@
   <int value="1441897340" label="AndroidSpellCheckerNonLowEnd:enabled"/>
   <int value="1442798825" label="enable-quic"/>
   <int value="1442830837" label="MemoryAblation:disabled"/>
-  <int value="1444320426" label="VizHitTestDrawQuad:enabled"/>
   <int value="1447295459" label="SyncPseudoUSSApps:enabled"/>
   <int value="1448684258" label="TabHoverCardImages:enabled"/>
   <int value="1452546183" label="PwaPersistentNotification:enabled"/>
@@ -45730,6 +45734,8 @@
   <int value="2" label="SchemaMetadataMissing"/>
   <int value="3" label="SchemaMetadataWrongVersion"/>
   <int value="4" label="ComponentMetadataMissing"/>
+  <int value="5" label="FetchedMetadataMissing"/>
+  <int value="6" label="ComponentAndFetchedMetadataMissing"/>
 </enum>
 
 <enum name="PreviewsHintCacheLevelDBStoreStatus">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 42eedab..92e271a 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -127120,6 +127120,26 @@
   </summary>
 </histogram>
 
+<histogram name="Tabs.TimeSinceLastView.OnTabClose" units="seconds"
+    expires_after="M82">
+  <owner>mattsimmons@chromium.org</owner>
+  <owner>memex-team@google.com</owner>
+  <summary>
+    When a tab is closed, this reports the amount of time in seconds that has
+    passed since the last user engagement with the tab. Currently Android-only.
+  </summary>
+</histogram>
+
+<histogram name="Tabs.TimeSinceLastView.OnTabView" units="seconds"
+    expires_after="M82">
+  <owner>mattsimmons@chromium.org</owner>
+  <owner>memex-team@google.com</owner>
+  <summary>
+    When a tab is revisited, this reports the amount of time in seconds that has
+    passed since the last user engagement with the tab. Currently Android-only.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Tabs.UnusedAndClosedInInterval.Count" units="tabs">
   <owner>sebmarchand@chromium.org</owner>
   <summary>
diff --git a/tools/win/DebugVisualizers/libc++.natvis b/tools/win/DebugVisualizers/libc++.natvis
index ae5101dd3..bed6f25 100644
--- a/tools/win/DebugVisualizers/libc++.natvis
+++ b/tools/win/DebugVisualizers/libc++.natvis
@@ -2,6 +2,28 @@
 <AutoVisualizer
   xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
 
+  <!-- libc++'s __compressed_pair is an internal type used pervasively for
+       doing the empty base class optimization.
+
+       __compressed_pair<U,V> derives from __compressed_pair_elem<U,0> and
+       __compressed_pair_elem<V,1>. __compressed_pair_elem<T> is specialized on
+       a 3rd template parameter:
+       * if T is empty and non-final the 3rd param is 1 and it derives from T
+       * else it has a member variable __value_ of type T
+  -->
+  <Type Name="std::__1::__compressed_pair_elem&lt;*,*,0&gt;">
+    <DisplayString>{__value_}</DisplayString>
+    <Expand>
+      <ExpandedItem>__value_</ExpandedItem>
+    </Expand>
+  </Type>
+  <Type Name="std::__1::__compressed_pair_elem&lt;*,*,1&gt;">
+    <DisplayString>{*($T1*)this}</DisplayString>
+    <Expand>
+      <ExpandedItem>*($T1*)this</ExpandedItem>
+    </Expand>
+  </Type>
+
   <!--libc++'s short string optimization:
       A basic_string is 3 size_t words long. In the "alternate string layout" that we use,
       they are: pointer to data, size, capacity.
@@ -79,6 +101,17 @@
     </Expand>
   </Type>
 
+  <Type Name="std::__1::unique_ptr&lt;*&gt;">
+    <Intrinsic Name="value" Expression="*($T1**)&amp;__ptr_" />
+    <SmartPointer Usage="Minimal">value()</SmartPointer>
+      <DisplayString Condition="value() == 0">empty</DisplayString>
+      <DisplayString Condition="value() != 0">
+        unique_ptr {value()}</DisplayString>
+      <Expand>
+        <Item Condition="value() != 0" Name="[ptr]">value()</Item>
+      </Expand>
+  </Type>
+
   <Type Name="std::__1::vector&lt;*&gt;">
     <Intrinsic Name="size" Expression="__end_ - __begin_" />
     <Expand>
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index f0764e5..012e3e6 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -1503,6 +1503,12 @@
       return "textPosition";
     case ax::mojom::IntAttribute::kTextStyle:
       return "textStyle";
+    case ax::mojom::IntAttribute::kTextOverlineStyle:
+      return "textOverlineStyle";
+    case ax::mojom::IntAttribute::kTextStrikethroughStyle:
+      return "textStrikethroughStyle";
+    case ax::mojom::IntAttribute::kTextUnderlineStyle:
+      return "textUnderlineStyle";
     case ax::mojom::IntAttribute::kPreviousFocusId:
       return "previousFocusId";
     case ax::mojom::IntAttribute::kNextFocusId:
@@ -1613,6 +1619,12 @@
     return ax::mojom::IntAttribute::kTextPosition;
   if (0 == strcmp(int_attribute, "textStyle"))
     return ax::mojom::IntAttribute::kTextStyle;
+  if (0 == strcmp(int_attribute, "textOverlineStyle"))
+    return ax::mojom::IntAttribute::kTextOverlineStyle;
+  if (0 == strcmp(int_attribute, "textStrikethroughStyle"))
+    return ax::mojom::IntAttribute::kTextStrikethroughStyle;
+  if (0 == strcmp(int_attribute, "textUnderlineStyle"))
+    return ax::mojom::IntAttribute::kTextUnderlineStyle;
   if (0 == strcmp(int_attribute, "previousFocusId"))
     return ax::mojom::IntAttribute::kPreviousFocusId;
   if (0 == strcmp(int_attribute, "nextFocusId"))
@@ -1962,6 +1974,42 @@
   return ax::mojom::MarkerType::kNone;
 }
 
+const char* ToString(ax::mojom::TextDecorationStyle text_decoration_style) {
+  switch (text_decoration_style) {
+    case ax::mojom::TextDecorationStyle::kNone:
+      return "none";
+    case ax::mojom::TextDecorationStyle::kSolid:
+      return "solid";
+    case ax::mojom::TextDecorationStyle::kDashed:
+      return "dashed";
+    case ax::mojom::TextDecorationStyle::kDotted:
+      return "dotted";
+    case ax::mojom::TextDecorationStyle::kDouble:
+      return "double";
+    case ax::mojom::TextDecorationStyle::kWavy:
+      return "wavy";
+  }
+
+  return "";
+}
+
+ax::mojom::TextDecorationStyle ParseTextDecorationStyle(
+    const char* text_decoration_style) {
+  if (0 == strcmp(text_decoration_style, "none"))
+    return ax::mojom::TextDecorationStyle::kNone;
+  if (0 == strcmp(text_decoration_style, "solid"))
+    return ax::mojom::TextDecorationStyle::kSolid;
+  if (0 == strcmp(text_decoration_style, "dashed"))
+    return ax::mojom::TextDecorationStyle::kDashed;
+  if (0 == strcmp(text_decoration_style, "dotted"))
+    return ax::mojom::TextDecorationStyle::kDotted;
+  if (0 == strcmp(text_decoration_style, "double"))
+    return ax::mojom::TextDecorationStyle::kDouble;
+  if (0 == strcmp(text_decoration_style, "wavy"))
+    return ax::mojom::TextDecorationStyle::kWavy;
+  return ax::mojom::TextDecorationStyle::kNone;
+}
+
 const char* ToString(ax::mojom::TextDirection text_direction) {
   switch (text_direction) {
     case ax::mojom::TextDirection::kNone:
@@ -2026,6 +2074,8 @@
       return "underline";
     case ax::mojom::TextStyle::kLineThrough:
       return "lineThrough";
+    case ax::mojom::TextStyle::kOverline:
+      return "overline";
     case ax::mojom::TextStyle::kNone:
       return "none";
   }
@@ -2042,6 +2092,8 @@
     return ax::mojom::TextStyle::kUnderline;
   if (0 == strcmp(text_style, "lineThrough"))
     return ax::mojom::TextStyle::kLineThrough;
+  if (0 == strcmp(text_style, "overline"))
+    return ax::mojom::TextStyle::kOverline;
   return ax::mojom::TextStyle::kNone;
 }
 
diff --git a/ui/accessibility/ax_enum_util.h b/ui/accessibility/ax_enum_util.h
index d558163a8..3b65385 100644
--- a/ui/accessibility/ax_enum_util.h
+++ b/ui/accessibility/ax_enum_util.h
@@ -74,6 +74,12 @@
 AX_EXPORT const char* ToString(ax::mojom::MarkerType marker_type);
 AX_EXPORT ax::mojom::MarkerType ParseMarkerType(const char* marker_type);
 
+// ax:mojom::TextDecorationStyle
+AX_EXPORT const char* ToString(
+    ax::mojom::TextDecorationStyle text_decoration_style);
+AX_EXPORT ax::mojom::TextDecorationStyle ParseTextDecorationStyle(
+    const char* text_decoration_style);
+
 // ax::mojom::TextDirection
 AX_EXPORT const char* ToString(ax::mojom::TextDirection text_direction);
 AX_EXPORT ax::mojom::TextDirection ParseTextDirection(
diff --git a/ui/accessibility/ax_enum_util_unittest.cc b/ui/accessibility/ax_enum_util_unittest.cc
index 4b248fc2..05b4d1c 100644
--- a/ui/accessibility/ax_enum_util_unittest.cc
+++ b/ui/accessibility/ax_enum_util_unittest.cc
@@ -135,6 +135,11 @@
   TestEnumStringConversion<ax::mojom::MarkerType>(ParseMarkerType);
 }
 
+TEST(AXEnumUtilTest, Text_Decoration_Style) {
+  TestEnumStringConversion<ax::mojom::TextDecorationStyle>(
+      ParseTextDecorationStyle);
+}
+
 TEST(AXEnumUtilTest, TextDirection) {
   TestEnumStringConversion<ax::mojom::TextDirection>(ParseTextDirection);
 }
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index b69cd18..6eeb9e8 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -587,6 +587,15 @@
    // Bold, italic, underline, etc.
   kTextStyle,
 
+  // The overline text decoration style.
+  kTextOverlineStyle,
+
+  // The strikethrough text decoration style.
+  kTextStrikethroughStyle,
+
+  // The underline text decoration style.
+  kTextUnderlineStyle,
+
    // Focus traversal in views and Android.
   kPreviousFocusId,
   kNextFocusId,
@@ -775,9 +784,19 @@
   kItalic,
   kUnderline,
   kLineThrough,
+  kOverline,
   kNone,
 };
 
+enum TextDecorationStyle {
+  kNone,
+  kDotted,
+  kDashed,
+  kSolid,
+  kDouble,
+  kWavy,
+};
+
 enum AriaCurrentState {
   kNone,
   kFalse,
diff --git a/ui/accessibility/ax_node_data.cc b/ui/accessibility/ax_node_data.cc
index bb50439..17e07bb 100644
--- a/ui/accessibility/ax_node_data.cc
+++ b/ui/accessibility/ax_node_data.cc
@@ -147,6 +147,9 @@
     case ax::mojom::IntAttribute::kTextDirection:
     case ax::mojom::IntAttribute::kTextPosition:
     case ax::mojom::IntAttribute::kTextStyle:
+    case ax::mojom::IntAttribute::kTextOverlineStyle:
+    case ax::mojom::IntAttribute::kTextStrikethroughStyle:
+    case ax::mojom::IntAttribute::kTextUnderlineStyle:
     case ax::mojom::IntAttribute::kAriaColumnCount:
     case ax::mojom::IntAttribute::kAriaCellColumnIndex:
     case ax::mojom::IntAttribute::kAriaRowCount:
@@ -1058,9 +1061,26 @@
           text_style_value += "underline,";
         if (HasTextStyle(ax::mojom::TextStyle::kLineThrough))
           text_style_value += "line-through,";
+        if (HasTextStyle(ax::mojom::TextStyle::kOverline))
+          text_style_value += "overline,";
         result += text_style_value.substr(0, text_style_value.size() - 1);
         break;
       }
+      case ax::mojom::IntAttribute::kTextOverlineStyle:
+        result += std::string(" text_overline_style=") +
+                  ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
+                      int_attribute.second));
+        break;
+      case ax::mojom::IntAttribute::kTextStrikethroughStyle:
+        result += std::string(" text_strikethrough_style=") +
+                  ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
+                      int_attribute.second));
+        break;
+      case ax::mojom::IntAttribute::kTextUnderlineStyle:
+        result += std::string(" text_underline_style=") +
+                  ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(
+                      int_attribute.second));
+        break;
       case ax::mojom::IntAttribute::kSetSize:
         result += " setsize=" + value;
         break;
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 9d047706..32d24aaa 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -1482,6 +1482,10 @@
   text_data.role = ax::mojom::Role::kStaticText;
   text_data.AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, "sans");
   text_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontWeight, 300);
+  text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle, 1);
+  text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle,
+                            2);
+  text_data.AddIntAttribute(ax::mojom::IntAttribute::kTextUnderlineStyle, 3);
   text_data.SetName("some text");
 
   ui::AXNodeData more_text_data;
@@ -1563,6 +1567,21 @@
                               expected_mixed_variant);
   expected_variant.Reset();
 
+  expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dot);
+  EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_OverlineStyleAttributeId,
+                              expected_variant);
+  expected_variant.Reset();
+
+  expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Dash);
+  EXPECT_UIA_TEXTATTRIBUTE_EQ(
+      text_range_provider, UIA_StrikethroughStyleAttributeId, expected_variant);
+  expected_variant.Reset();
+
+  expected_variant.Set(TextDecorationLineStyle::TextDecorationLineStyle_Single);
+  EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider,
+                              UIA_UnderlineStyleAttributeId, expected_variant);
+  expected_variant.Reset();
+
   base::string16 style_name = base::UTF8ToUTF16("");
   expected_variant.Set(SysAllocString(style_name.c_str()));
   EXPECT_UIA_TEXTATTRIBUTE_EQ(text_range_provider, UIA_StyleNameAttributeId,
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index cdf0a9058..ffe54e4 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -4040,10 +4040,25 @@
       V_VT(result) = VT_BOOL;
       V_BOOL(result) = IsInvisibleOrIgnored() ? VARIANT_TRUE : VARIANT_FALSE;
       break;
+    case UIA_OverlineStyleAttributeId:
+      V_VT(result) = VT_I4;
+      V_I4(result) = GetUIATextDecorationStyle(
+          ax::mojom::IntAttribute::kTextOverlineStyle);
+      break;
+    case UIA_StrikethroughStyleAttributeId:
+      V_VT(result) = VT_I4;
+      V_I4(result) = GetUIATextDecorationStyle(
+          ax::mojom::IntAttribute::kTextStrikethroughStyle);
+      break;
     case UIA_StyleNameAttributeId:
       V_VT(result) = VT_BSTR;
       V_BSTR(result) = GetStyleNameAttributeAsBSTR();
       break;
+    case UIA_UnderlineStyleAttributeId:
+      V_VT(result) = VT_I4;
+      V_I4(result) = GetUIATextDecorationStyle(
+          ax::mojom::IntAttribute::kTextUnderlineStyle);
+      break;
     default:
       V_VT(result) = VT_UNKNOWN;
       return ::UiaGetReservedNotSupportedValue(&V_UNKNOWN(result));
@@ -6577,6 +6592,28 @@
   return SysAllocString(style_name.c_str());
 }
 
+TextDecorationLineStyle AXPlatformNodeWin::GetUIATextDecorationStyle(
+    const ax::mojom::IntAttribute int_attribute) const {
+  const ax::mojom::TextDecorationStyle text_decoration_style =
+      static_cast<ax::mojom::TextDecorationStyle>(
+          GetIntAttribute(int_attribute));
+
+  switch (text_decoration_style) {
+    case ax::mojom::TextDecorationStyle::kNone:
+      return TextDecorationLineStyle::TextDecorationLineStyle_None;
+    case ax::mojom::TextDecorationStyle::kDotted:
+      return TextDecorationLineStyle::TextDecorationLineStyle_Dot;
+    case ax::mojom::TextDecorationStyle::kDashed:
+      return TextDecorationLineStyle::TextDecorationLineStyle_Dash;
+    case ax::mojom::TextDecorationStyle::kSolid:
+      return TextDecorationLineStyle::TextDecorationLineStyle_Single;
+    case ax::mojom::TextDecorationStyle::kDouble:
+      return TextDecorationLineStyle::TextDecorationLineStyle_Double;
+    case ax::mojom::TextDecorationStyle::kWavy:
+      return TextDecorationLineStyle::TextDecorationLineStyle_Wavy;
+  }
+}
+
 // IRawElementProviderSimple support methods.
 
 AXPlatformNodeWin::PatternProviderFactoryMethod
diff --git a/ui/accessibility/platform/ax_platform_node_win.h b/ui/accessibility/platform/ax_platform_node_win.h
index ee9e2a9..7efa9ec 100644
--- a/ui/accessibility/platform/ax_platform_node_win.h
+++ b/ui/accessibility/platform/ax_platform_node_win.h
@@ -1211,6 +1211,10 @@
   // Helper to get the UIA StyleName for this node as a BSTR.
   BSTR GetStyleNameAttributeAsBSTR() const;
 
+  // Gets the TextDecorationLineStyle based on the provided int attribute.
+  TextDecorationLineStyle GetUIATextDecorationStyle(
+      const ax::mojom::IntAttribute int_attribute) const;
+
   // IRawElementProviderSimple support methods.
 
   using PatternProviderFactoryMethod = HRESULT (*)(AXPlatformNodeWin*,
diff --git a/ui/aura/mus/in_flight_change.cc b/ui/aura/mus/in_flight_change.cc
index a24ff32..946ff5ce 100644
--- a/ui/aura/mus/in_flight_change.cc
+++ b/ui/aura/mus/in_flight_change.cc
@@ -85,12 +85,14 @@
     WindowTreeClient* window_tree_client,
     WindowMus* window,
     const gfx::Rect& revert_bounds,
+    ui::WindowShowState revert_show_state,
     bool from_server,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         revert_local_surface_id_allocation)
     : InFlightChange(window, ChangeType::BOUNDS),
       window_tree_client_(window_tree_client),
       revert_bounds_(revert_bounds),
+      revert_show_state_(revert_show_state),
       from_server_(from_server),
       revert_local_surface_id_allocation_(revert_local_surface_id_allocation) {
   // Should always be created with a window.
@@ -100,17 +102,17 @@
 InFlightBoundsChange::~InFlightBoundsChange() {}
 
 void InFlightBoundsChange::SetRevertValueFrom(const InFlightChange& change) {
-  from_server_ = static_cast<const InFlightBoundsChange&>(change).from_server_;
-  revert_bounds_ =
-      static_cast<const InFlightBoundsChange&>(change).revert_bounds_;
+  const auto& from = static_cast<const InFlightBoundsChange&>(change);
+  from_server_ = from.from_server_;
+  revert_bounds_ = from.revert_bounds_;
+  revert_show_state_ = from.revert_show_state_;
   revert_local_surface_id_allocation_ =
-      static_cast<const InFlightBoundsChange&>(change)
-          .revert_local_surface_id_allocation_;
+      from.revert_local_surface_id_allocation_;
 }
 
 void InFlightBoundsChange::Revert() {
   window_tree_client_->SetWindowBoundsFromServer(
-      window(), revert_bounds_, from_server_,
+      window(), revert_bounds_, revert_show_state_, from_server_,
       revert_local_surface_id_allocation_);
 }
 
diff --git a/ui/aura/mus/in_flight_change.h b/ui/aura/mus/in_flight_change.h
index 9fd7f4a7..97ad8de0 100644
--- a/ui/aura/mus/in_flight_change.h
+++ b/ui/aura/mus/in_flight_change.h
@@ -155,6 +155,7 @@
   InFlightBoundsChange(WindowTreeClient* window_tree_client,
                        WindowMus* window,
                        const gfx::Rect& revert_bounds,
+                       ui::WindowShowState revert_show_state,
                        bool from_server,
                        const base::Optional<viz::LocalSurfaceIdAllocation>&
                            local_surface_id_allocation);
@@ -168,6 +169,7 @@
  private:
   WindowTreeClient* window_tree_client_;
   gfx::Rect revert_bounds_;
+  ui::WindowShowState revert_show_state_;
   // If true the change originated from the server. If false, the change was
   // initiated by this client.
   bool from_server_;
diff --git a/ui/aura/mus/window_port_mus.cc b/ui/aura/mus/window_port_mus.cc
index 579ecdf..404043f 100644
--- a/ui/aura/mus/window_port_mus.cc
+++ b/ui/aura/mus/window_port_mus.cc
@@ -718,6 +718,14 @@
   if (key == client::kDragDropDelegateKey) {
     SetCanAcceptDrops(window_->GetProperty(client::kDragDropDelegateKey) !=
                       nullptr);
+  } else if (key == client::kShowStateKey &&
+             WindowTreeHostMus::ForWindow(window_) &&
+             WindowTreeHostMus::ForWindow(window_)
+                 ->is_server_setting_bounds()) {
+    // When the server is setting the bounds, it also provides a show state.
+    // When that's being applied, don't report the show state back to the
+    // server.
+    return;
   }
 
   ServerChangeData change_data;
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc
index 5e93250..db7aff9 100644
--- a/ui/aura/mus/window_tree_client.cc
+++ b/ui/aura/mus/window_tree_client.cc
@@ -510,7 +510,8 @@
                              window_data.visible);
   WindowMus* window = WindowMus::Get(window_tree_host->window());
 
-  SetWindowBoundsFromServer(window, window_data.bounds, /* from_server */ true,
+  SetWindowBoundsFromServer(window, window_data.bounds, window_data.state,
+                            /* from_server */ true,
                             local_surface_id_allocation);
   return window_tree_host;
 }
@@ -634,6 +635,7 @@
 void WindowTreeClient::SetWindowBoundsFromServer(
     WindowMus* window,
     const gfx::Rect& revert_bounds,
+    ui::WindowShowState state,
     bool from_server,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         local_surface_id_allocation) {
@@ -661,7 +663,7 @@
     window_tree_host->TakePendingLocalSurfaceIdFromServer();
   window->UpdateLocalSurfaceIdFromParent(*local_surface_id_allocation);
 
-  window_tree_host->SetBoundsFromServer(revert_bounds,
+  window_tree_host->SetBoundsFromServer(revert_bounds, state,
                                         window->GetLocalSurfaceIdAllocation());
 
   window->DidSetWindowTreeHostBoundsFromServer();
@@ -681,7 +683,7 @@
     const viz::LocalSurfaceIdAllocation lsia =
         window->GetWindow()->GetLocalSurfaceIdAllocation();
     window_tree_host->SetBoundsFromServer(window_tree_host->bounds_in_dip(),
-                                          lsia);
+                                          ui::SHOW_STATE_DEFAULT, lsia);
     // Send the newly generated id to the server. This does *not* use
     // WindowTreeHost:SetBounds() (which notifies the server of a bounds and id)
     // as WindowTreeHost::SetBounds() leads to race conditions. In particular,
@@ -691,7 +693,7 @@
     tree_->UpdateLocalSurfaceIdFromChild(window->server_id(), lsia);
   } else {
     window_tree_host->SetBoundsFromServer(
-        window_tree_host->bounds_in_dip(),
+        window_tree_host->bounds_in_dip(), ui::SHOW_STATE_DEFAULT,
         window->GetLocalSurfaceIdAllocation());
   }
   DCHECK(!window_tree_host->has_pending_local_surface_id_from_server());
@@ -741,8 +743,8 @@
   }
   const uint32_t change_id =
       ScheduleInFlightChange(std::make_unique<InFlightBoundsChange>(
-          this, window, old_bounds_in_dip, /* from_server */ false,
-          local_surface_id_allocation));
+          this, window, old_bounds_in_dip, ui::SHOW_STATE_DEFAULT,
+          /* from_server */ false, local_surface_id_allocation));
   tree_->SetWindowBounds(change_id, window->server_id(), new_bounds_in_dip,
                          local_surface_id_allocation);
 }
@@ -1173,7 +1175,7 @@
 
   const gfx::Rect bounds(data->bounds);
   {
-    InFlightBoundsChange bounds_change(this, window, bounds,
+    InFlightBoundsChange bounds_change(this, window, bounds, data->state,
                                        /* from_server */ true,
                                        local_surface_id_allocation);
     InFlightChange* current_change =
@@ -1186,12 +1188,14 @@
           local_surface_id_allocation);
     } else if (window->GetWindow()->GetBoundsInScreen() != bounds) {
       window->UpdateLocalSurfaceIdFromParent(local_surface_id_allocation);
-      SetWindowBoundsFromServer(window, bounds, /* from_server */ true,
+      SetWindowBoundsFromServer(window, bounds, data->state,
+                                /* from_server */ true,
                                 window->GetLocalSurfaceIdAllocation());
     } else {
       // No pending changes and the bounds match that of the server. Call
       // SetWindowBoundsFromServer() to apply |local_surface_id_allocation|.
-      SetWindowBoundsFromServer(window, bounds, /* from_server */ true,
+      SetWindowBoundsFromServer(window, bounds, data->state,
+                                /* from_server */ true,
                                 local_surface_id_allocation);
     }
   }
@@ -1219,6 +1223,7 @@
 void WindowTreeClient::OnWindowBoundsChanged(
     ws::Id window_id,
     const gfx::Rect& new_bounds,
+    ui::WindowShowState state,
     const base::Optional<viz::LocalSurfaceIdAllocation>&
         local_surface_id_allocation) {
   WindowMus* window = GetWindowByServerId(window_id);
@@ -1233,7 +1238,7 @@
   }
   TRACE_EVENT0("ui", "WindowTreeClient::OnWindowBoundsChanged");
 
-  InFlightBoundsChange new_change(this, window, new_bounds,
+  InFlightBoundsChange new_change(this, window, new_bounds, state,
                                   /* from_server */ true,
                                   local_surface_id_allocation);
 
@@ -1263,7 +1268,7 @@
     return;
   }
 
-  SetWindowBoundsFromServer(window, new_bounds, /* from_server */ true,
+  SetWindowBoundsFromServer(window, new_bounds, state, /* from_server */ true,
                             local_surface_id_allocation);
 }
 
diff --git a/ui/aura/mus/window_tree_client.h b/ui/aura/mus/window_tree_client.h
index 04a0a3d..815fdaf3 100644
--- a/ui/aura/mus/window_tree_client.h
+++ b/ui/aura/mus/window_tree_client.h
@@ -337,6 +337,7 @@
   void SetWindowBoundsFromServer(
       WindowMus* window,
       const gfx::Rect& revert_bounds,
+      ui::WindowShowState state,
       bool from_server,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation);
@@ -415,6 +416,7 @@
   void OnWindowBoundsChanged(
       ws::Id window_id,
       const gfx::Rect& new_bounds,
+      ui::WindowShowState state,
       const base::Optional<viz::LocalSurfaceIdAllocation>&
           local_surface_id_allocation) override;
   void OnWindowTransformChanged(ws::Id window_id,
diff --git a/ui/aura/mus/window_tree_client_unittest.cc b/ui/aura/mus/window_tree_client_unittest.cc
index 330962f..1e434143 100644
--- a/ui/aura/mus/window_tree_client_unittest.cc
+++ b/ui/aura/mus/window_tree_client_unittest.cc
@@ -158,6 +158,49 @@
   DISALLOW_COPY_AND_ASSIGN(TestEventObserver);
 };
 
+// Helps verify that updates to window bounds and window state are kept in sync
+// from the perspective of the WindowObserver.
+class WindowBoundsChangeVerifier : public WindowObserver {
+ public:
+  WindowBoundsChangeVerifier(aura::Window* window,
+                             ui::WindowShowState expected_state,
+                             const gfx::Rect& expected_bounds)
+      : window_(window),
+        expected_state_(expected_state),
+        expected_bounds_(expected_bounds) {
+    window->AddObserver(this);
+  }
+
+  ~WindowBoundsChangeVerifier() override { window_->RemoveObserver(this); }
+
+  // WindowObserver:
+  void OnWindowPropertyChanged(Window* window,
+                               const void* key,
+                               intptr_t old) override {
+    if (key == client::kShowStateKey)
+      Verify();
+  }
+
+  void OnWindowBoundsChanged(Window* window,
+                             const gfx::Rect& old_bounds,
+                             const gfx::Rect& new_bounds,
+                             ui::PropertyChangeReason reason) override {
+    Verify();
+  }
+
+ private:
+  void Verify() {
+    EXPECT_EQ(expected_state_, window_->GetProperty(client::kShowStateKey));
+    EXPECT_EQ(expected_bounds_, window_->bounds());
+  }
+
+  Window* window_;
+  const ui::WindowShowState expected_state_;
+  const gfx::Rect expected_bounds_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowBoundsChangeVerifier);
+};
+
 }  // namespace
 
 class WindowTreeClientTest : public test::AuraMusClientTestBase {
@@ -526,7 +569,7 @@
           viz::LocalSurfaceId(1, base::UnguessableToken::Create()),
           base::TimeTicks::Now());
   window_tree_client()->OnWindowBoundsChanged(
-      server_id(&root_window), server_changed_bounds,
+      server_id(&root_window), server_changed_bounds, ui::SHOW_STATE_DEFAULT,
       server_changed_local_surface_id_allocation);
 
   WindowMus* root_window_mus = WindowMus::Get(&root_window);
@@ -546,8 +589,9 @@
             root_window_mus->GetLocalSurfaceIdAllocation());
 
   // Simulate server changing back to original bounds. Should take immediately.
-  window_tree_client()->OnWindowBoundsChanged(server_id(&root_window),
-                                              original_bounds, base::nullopt);
+  window_tree_client()->OnWindowBoundsChanged(
+      server_id(&root_window), original_bounds, ui::SHOW_STATE_DEFAULT,
+      base::nullopt);
   EXPECT_EQ(original_bounds, root_window.bounds());
 }
 
@@ -2497,7 +2541,7 @@
   const viz::LocalSurfaceIdAllocation lsia2 =
       GenerateLocalSurfaceIdForNewTopLevel();
   window_tree_client()->OnWindowBoundsChanged(server_id(top_level), bounds,
-                                              lsia2);
+                                              ui::SHOW_STATE_DEFAULT, lsia2);
   EXPECT_EQ(0u,
             window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
   // The local surface id is updated from lsia2, so it won't match with either.
@@ -2505,19 +2549,52 @@
   EXPECT_EQ(lsia2, top_level->GetLocalSurfaceIdAllocation());
 }
 
+// Regression test for https://crbug.com/943509
+TEST_F(WindowTreeClientTest, SetBoundsAlsoChangesShowState) {
+  const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
+  ASSERT_NE(new_bounds, root_window()->bounds());
+  root_window()->SetBounds(new_bounds);
+  EXPECT_EQ(new_bounds, root_window()->bounds());
+
+  root_window()->SetProperty(aura::client::kShowStateKey,
+                             ui::SHOW_STATE_NORMAL);
+
+  gfx::Rect maximized_bounds(0, 0, 500, 500);
+  WindowBoundsChangeVerifier verifier(root_window(), ui::SHOW_STATE_MAXIMIZED,
+                                      maximized_bounds);
+  window_tree_client()->OnWindowBoundsChanged(
+      server_id(root_window()), maximized_bounds, ui::SHOW_STATE_MAXIMIZED,
+      GenerateLocalSurfaceIdForNewTopLevel());
+}
+
 TEST_F(WindowTreeClientTestHighDPI, SetBounds) {
   const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
   ASSERT_NE(new_bounds, root_window()->bounds());
   root_window()->SetBounds(new_bounds);
   EXPECT_EQ(new_bounds, root_window()->bounds());
 
+  root_window()->SetProperty(aura::client::kShowStateKey,
+                             ui::SHOW_STATE_NORMAL);
+
   // Simulate the server responding with a bounds change. Server operates in
   // dips.
   const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 200, 200));
   window_tree_client()->OnWindowBoundsChanged(
-      server_id(root_window()), server_changed_bounds,
+      server_id(root_window()), server_changed_bounds, ui::SHOW_STATE_DEFAULT,
       GenerateLocalSurfaceIdForNewTopLevel());
   EXPECT_EQ(server_changed_bounds, root_window()->bounds());
+  EXPECT_EQ(ui::SHOW_STATE_NORMAL,
+            root_window()->GetProperty(aura::client::kShowStateKey));
+
+  // Simulate the server responding with a bounds change along with a state
+  // update. Both should take effect.
+  const gfx::Rect server_changed_bounds2(gfx::Rect(0, 0, 300, 200));
+  window_tree_client()->OnWindowBoundsChanged(
+      server_id(root_window()), server_changed_bounds2,
+      ui::SHOW_STATE_MAXIMIZED, GenerateLocalSurfaceIdForNewTopLevel());
+  EXPECT_EQ(server_changed_bounds2, root_window()->bounds());
+  EXPECT_EQ(ui::SHOW_STATE_MAXIMIZED,
+            root_window()->GetProperty(aura::client::kShowStateKey));
 }
 
 TEST_F(WindowTreeClientTestHighDPI, NewTopLevelWindowBounds) {
@@ -3078,7 +3155,7 @@
   parent_local_surface_id_allocator_.GenerateId();
   const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 200, 200));
   window_tree_client()->OnWindowBoundsChanged(
-      server_id(root), server_changed_bounds,
+      server_id(root), server_changed_bounds, ui::SHOW_STATE_DEFAULT,
       parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation());
   const viz::LocalSurfaceId local_surface_id1 =
       root->GetLocalSurfaceIdAllocation().local_surface_id();
diff --git a/ui/aura/mus/window_tree_host_mus.cc b/ui/aura/mus/window_tree_host_mus.cc
index 4bd6f08..babd2f46 100644
--- a/ui/aura/mus/window_tree_host_mus.cc
+++ b/ui/aura/mus/window_tree_host_mus.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
 #include "ui/aura/mus/input_method_mus.h"
 #include "ui/aura/mus/mus_types.h"
@@ -165,7 +166,11 @@
           std::make_unique<Window>(nullptr,
                                    std::move(init_params.window_port))),
       display_id_(init_params.display_id),
-      delegate_(init_params.window_tree_client) {
+      delegate_(init_params.window_tree_client),
+      show_state_observer_(
+          window(),
+          base::BindRepeating(&WindowTreeHostMus::OnWindowShowStateDidChange,
+                              base::Unretained(this))) {
   gfx::Rect bounds_in_pixels;
   window()->SetProperty(kWindowTreeHostMusKey, this);
   // TODO(sky): find a cleaner way to set this! Revisit this now that
@@ -294,8 +299,23 @@
 
 void WindowTreeHostMus::SetBoundsFromServer(
     const gfx::Rect& bounds,
+    ui::WindowShowState state,
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
   base::AutoReset<bool> resetter(&is_server_setting_bounds_, true);
+  // When there's a non-default |state|, we want to set that property and then
+  // the bounds, so that by the time client code observes a bounds change the
+  // show state is already updated, and by the time client code observes a state
+  // change the bounds are already updated as well. To do this, we set the state
+  // here, and as the first WindowObserver on |window()|, update the bounds.
+  if (state != ui::SHOW_STATE_DEFAULT &&
+      window()->GetProperty(aura::client::kShowStateKey) != state) {
+    server_bounds_ = &bounds;
+    server_lsia_ = &local_surface_id_allocation;
+    window()->SetProperty(aura::client::kShowStateKey, state);
+    DCHECK(!server_bounds_);
+    DCHECK(!server_lsia_);
+    return;
+  }
   SetBounds(bounds, local_surface_id_allocation);
 }
 
@@ -429,14 +449,52 @@
   return true;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// WindowTreeHostMus, protected:
+
 void WindowTreeHostMus::SetBoundsInPixels(
     const gfx::Rect& bounds,
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
   // As UI code operates in DIPs (as does the window-service APIs), this
-  // function is very seldomly uses, and so converts to DIPs.
+  // function is very seldomly used, and so converts to DIPs.
   SetBounds(
       gfx::ConvertRectToDIP(ui::GetScaleFactorForNativeView(window()), bounds),
       local_surface_id_allocation);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// WindowTreeHostMus, private:
+
+void WindowTreeHostMus::OnWindowShowStateDidChange() {
+  if (!server_bounds_)
+    return;
+
+  DCHECK(is_server_setting_bounds_);
+  DCHECK(server_lsia_);
+  SetBounds(*server_bounds_, *server_lsia_);
+  server_bounds_ = nullptr;
+  server_lsia_ = nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WindowTreeHostMus::WindowShowStateChangeObserver, public:
+
+WindowTreeHostMus::WindowShowStateChangeObserver::WindowShowStateChangeObserver(
+    aura::Window* window,
+    base::RepeatingClosure show_state_changed_callback)
+    : show_state_changed_callback_(show_state_changed_callback) {
+  window->AddObserver(this);
+}
+
+WindowTreeHostMus::WindowShowStateChangeObserver::
+    ~WindowShowStateChangeObserver() = default;
+
+void WindowTreeHostMus::WindowShowStateChangeObserver::OnWindowPropertyChanged(
+    aura::Window* window,
+    const void* key,
+    intptr_t old) {
+  if (key == client::kShowStateKey)
+    show_state_changed_callback_.Run();
+}
+
 }  // namespace aura
diff --git a/ui/aura/mus/window_tree_host_mus.h b/ui/aura/mus/window_tree_host_mus.h
index 25037ba81..bc1a37b 100644
--- a/ui/aura/mus/window_tree_host_mus.h
+++ b/ui/aura/mus/window_tree_host_mus.h
@@ -15,6 +15,7 @@
 #include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/aura/aura_export.h"
 #include "ui/aura/mus/input_method_mus_delegate.h"
+#include "ui/aura/window_observer.h"
 #include "ui/aura/window_tree_host_platform.h"
 #include "ui/base/ime/mojo/ime.mojom.h"
 #include "ui/base/mojo/ui_base_types.mojom.h"
@@ -46,6 +47,7 @@
       const viz::LocalSurfaceIdAllocation& local_surface_id_allocation);
   void SetBoundsFromServer(
       const gfx::Rect& bounds,
+      ui::WindowShowState state,
       const viz::LocalSurfaceIdAllocation& local_surface_id_allocation);
   const gfx::Rect& bounds_in_dip() const { return bounds_in_dip_; }
 
@@ -124,6 +126,8 @@
   bool ConnectToImeEngine(ime::mojom::ImeEngineRequest engine_request,
                           ime::mojom::ImeEngineClientPtr client) override;
 
+  bool is_server_setting_bounds() const { return is_server_setting_bounds_; }
+
  protected:
   // This is in the protected section as SetBounds() is preferred.
   // aura::WindowTreeHostPlatform:
@@ -132,9 +136,29 @@
       const viz::LocalSurfaceIdAllocation& local_surface_id_allocation =
           viz::LocalSurfaceIdAllocation()) override;
 
-  bool is_server_setting_bounds() const { return is_server_setting_bounds_; }
-
  private:
+  // This class observes |window()| and runs a callback every time the show
+  // state has changed.
+  class WindowShowStateChangeObserver : public WindowObserver {
+   public:
+    WindowShowStateChangeObserver(
+        aura::Window* window,
+        base::RepeatingClosure show_state_changed_callback);
+    ~WindowShowStateChangeObserver() override;
+
+    // WindowObserver:
+    void OnWindowPropertyChanged(aura::Window* window,
+                                 const void* key,
+                                 intptr_t old) override;
+
+   private:
+    base::RepeatingClosure show_state_changed_callback_;
+
+    DISALLOW_COPY_AND_ASSIGN(WindowShowStateChangeObserver);
+  };
+
+  void OnWindowShowStateDidChange();
+
   int64_t display_id_;
 
   WindowTreeHostMusDelegate* delegate_;
@@ -142,6 +166,13 @@
   // If true, the server initiated the bounds change.
   bool is_server_setting_bounds_ = false;
 
+  // These two properties point to the arguments passed into
+  // SetBoundsFromServer, or null if that method isn't currently on the call
+  // stack. They're only set when the window's show state needs to change in
+  // addition to the bounds.
+  const gfx::Rect* server_bounds_ = nullptr;
+  const viz::LocalSurfaceIdAllocation* server_lsia_ = nullptr;
+
   std::unique_ptr<InputMethodMus> input_method_mus_;
 
   base::Optional<viz::LocalSurfaceIdAllocation>
@@ -149,6 +180,10 @@
 
   gfx::Rect bounds_in_dip_;
 
+  // Start observing the window early (at construction time), because |this|
+  // needs to receive updates to the show state before other observers.
+  WindowShowStateChangeObserver show_state_observer_;
+
   DISALLOW_COPY_AND_ASSIGN(WindowTreeHostMus);
 };
 
diff --git a/ui/base/mojo/ui_base_types.mojom b/ui/base/mojo/ui_base_types.mojom
index ab2d4e5..00b6d98 100644
--- a/ui/base/mojo/ui_base_types.mojom
+++ b/ui/base/mojo/ui_base_types.mojom
@@ -4,6 +4,16 @@
 
 module ui.mojom;
 
+// Window "show" state.
+enum WindowShowState {
+  kDefault,
+  kNormal,
+  kMinimized,
+  kMaximized,
+  kInactive,
+  kFullscreen,
+};
+
 // Dialog button identifiers used to specify which buttons to show the user.
 enum DialogButton {
   NONE,
diff --git a/ui/base/mojo/ui_base_types.typemap b/ui/base/mojo/ui_base_types.typemap
index 9bad08ab..b60fb698 100644
--- a/ui/base/mojo/ui_base_types.typemap
+++ b/ui/base/mojo/ui_base_types.typemap
@@ -15,4 +15,5 @@
 
   # Note that HitTestCompat can't be used because it's not defined in Windows.
   "ui.mojom.HitTest=int",
+  "ui.mojom.WindowShowState=ui::WindowShowState",
 ]
diff --git a/ui/base/mojo/ui_base_types_struct_traits.h b/ui/base/mojo/ui_base_types_struct_traits.h
index c91371d..3b61034ca 100644
--- a/ui/base/mojo/ui_base_types_struct_traits.h
+++ b/ui/base/mojo/ui_base_types_struct_traits.h
@@ -5,9 +5,14 @@
 #ifndef UI_BASE_MOJO_UI_BASE_TYPES_STRUCT_TRAITS_H_
 #define UI_BASE_MOJO_UI_BASE_TYPES_STRUCT_TRAITS_H_
 
+#if defined(OS_WIN)
+#include <windows.h>
+#else
+#include "ui/base/hit_test.h"
+#endif
+
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/enum_traits.h"
-#include "ui/base/hit_test.h"
 #include "ui/base/mojo/ui_base_types.mojom.h"
 #include "ui/base/ui_base_types.h"
 
@@ -246,6 +251,57 @@
   }
 };
 
+template <>
+struct EnumTraits<ui::mojom::WindowShowState, ui::WindowShowState> {
+  static ui::mojom::WindowShowState ToMojom(ui::WindowShowState modal_type) {
+    switch (modal_type) {
+      case ui::SHOW_STATE_DEFAULT:
+        return ui::mojom::WindowShowState::kDefault;
+      case ui::SHOW_STATE_NORMAL:
+        return ui::mojom::WindowShowState::kNormal;
+      case ui::SHOW_STATE_MINIMIZED:
+        return ui::mojom::WindowShowState::kMinimized;
+      case ui::SHOW_STATE_MAXIMIZED:
+        return ui::mojom::WindowShowState::kMaximized;
+      case ui::SHOW_STATE_INACTIVE:
+        return ui::mojom::WindowShowState::kInactive;
+      case ui::SHOW_STATE_FULLSCREEN:
+        return ui::mojom::WindowShowState::kFullscreen;
+      case ui::SHOW_STATE_END:
+        NOTREACHED();
+        break;
+    }
+    NOTREACHED();
+    return ui::mojom::WindowShowState::kDefault;
+  }
+
+  static bool FromMojom(ui::mojom::WindowShowState modal_type,
+                        ui::WindowShowState* out) {
+    switch (modal_type) {
+      case ui::mojom::WindowShowState::kDefault:
+        *out = ui::SHOW_STATE_DEFAULT;
+        return true;
+      case ui::mojom::WindowShowState::kNormal:
+        *out = ui::SHOW_STATE_NORMAL;
+        return true;
+      case ui::mojom::WindowShowState::kMinimized:
+        *out = ui::SHOW_STATE_MINIMIZED;
+        return true;
+      case ui::mojom::WindowShowState::kMaximized:
+        *out = ui::SHOW_STATE_MAXIMIZED;
+        return true;
+      case ui::mojom::WindowShowState::kInactive:
+        *out = ui::SHOW_STATE_INACTIVE;
+        return true;
+      case ui::mojom::WindowShowState::kFullscreen:
+        *out = ui::SHOW_STATE_FULLSCREEN;
+        return true;
+    }
+    NOTREACHED();
+    return false;
+  }
+};
+
 }  // namespace mojo
 
 #endif  // UI_BASE_MOJO_WINDOW_OPEN_DISPOSITION_STRUCT_TRAITS_H_
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index 79fcc7d3..a8ba54425 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -61,6 +61,10 @@
 #include "ui/gfx/interpolated_transform.h"
 #include "ui/gfx/skia_util.h"
 
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
 using cc::MatchesPNGFile;
 using cc::WritePNGFile;
 
@@ -134,7 +138,7 @@
   LayerWithRealCompositorTest()
       : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::UI) {
-    gfx::FontList::SetDefaultFontDescription("Arial, Times New Roman, 15px");
+    gfx::FontList::SetDefaultFontDescription("Segoe UI, 15px");
   }
   ~LayerWithRealCompositorTest() override {}
 
@@ -1890,7 +1894,12 @@
   ReadPixels(&bitmap);
   ASSERT_FALSE(bitmap.empty());
 
-  base::FilePath ref_img = test_data_dir().AppendASCII("string_faded.png");
+  std::string filename;
+  if (base::win::GetVersion() < base::win::VERSION_WIN10)
+    filename = "string_faded_win7.png";
+  else
+    filename = "string_faded_win10.png";
+  base::FilePath ref_img = test_data_dir().AppendASCII(filename);
   // WritePNGFile(bitmap, ref_img, true);
 
   float percentage_pixels_large_error = 8.0f;  // 200px / (50*50)
diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc
index 25b00ba..59fe1faa 100644
--- a/ui/gfx/platform_font_win.cc
+++ b/ui/gfx/platform_font_win.cc
@@ -37,6 +37,7 @@
 #include "ui/gfx/font.h"
 #include "ui/gfx/font_render_params.h"
 #include "ui/gfx/system_fonts_win.h"
+#include "ui/gfx/win/direct_write.h"
 #include "ui/gfx/win/scoped_set_map_mode.h"
 
 namespace {
@@ -238,8 +239,6 @@
 // static
 PlatformFontWin::HFontRef* PlatformFontWin::base_font_ref_;
 
-IDWriteFactory* PlatformFontWin::direct_write_factory_ = nullptr;
-
 // TODO(ananta)
 // Remove the CHECKs in this function once this stabilizes on the field.
 HRESULT GetFamilyNameFromDirectWriteFont(IDWriteFont* dwrite_font,
@@ -277,19 +276,6 @@
   InitWithFontNameAndSize(font_name, font_size);
 }
 
-// static
-void PlatformFontWin::SetDirectWriteFactory(IDWriteFactory* factory) {
-  // We grab a reference on the DirectWrite factory. This reference is
-  // leaked, which is ok because skia leaks it as well.
-  factory->AddRef();
-  direct_write_factory_ = factory;
-}
-
-// static
-bool PlatformFontWin::IsDirectWriteEnabled() {
-  return direct_write_factory_ != nullptr;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // PlatformFontWin, PlatformFont implementation:
 
@@ -425,10 +411,7 @@
     GetTextMetricsForFont(screen_dc, font, &font_metrics);
   }
 
-  if (IsDirectWriteEnabled())
-    return CreateHFontRefFromSkia(font, font_metrics);
-
-  return CreateHFontRefFromGDI(font, font_metrics);
+  return CreateHFontRefFromSkia(font, font_metrics);
 }
 
 PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRefFromGDI(
@@ -489,7 +472,7 @@
   // DirectWrite to calculate the cap height.
   Microsoft::WRL::ComPtr<IDWriteFont> dwrite_font;
   HRESULT hr = GetMatchingDirectWriteFont(
-      &font_info, italic, direct_write_factory_, dwrite_font.GetAddressOf());
+      &font_info, italic, win::GetDirectWriteFactory(), &dwrite_font);
   if (FAILED(hr)) {
     CHECK(false);
     return nullptr;
diff --git a/ui/gfx/platform_font_win.h b/ui/gfx/platform_font_win.h
index 97ce5073..b4e538e 100644
--- a/ui/gfx/platform_font_win.h
+++ b/ui/gfx/platform_font_win.h
@@ -60,8 +60,6 @@
   // from skia and DirectWrite.
   static void SetDirectWriteFactory(IDWriteFactory* factory);
 
-  static bool IsDirectWriteEnabled();
-
  private:
   FRIEND_TEST_ALL_PREFIXES(PlatformFontWinTest, Metrics_SkiaVersusGDI);
   FRIEND_TEST_ALL_PREFIXES(PlatformFontWinTest, DirectWriteFontSubstitution);
@@ -191,9 +189,6 @@
   // Indirect reference to the HFontRef, which references the underlying HFONT.
   scoped_refptr<HFontRef> font_ref_;
 
-  // Pointer to the global IDWriteFactory interface.
-  static IDWriteFactory* direct_write_factory_;
-
   DISALLOW_COPY_AND_ASSIGN(PlatformFontWin);
 };
 
diff --git a/ui/gfx/test/data/compositor/string_faded.png b/ui/gfx/test/data/compositor/string_faded.png
deleted file mode 100644
index ec991520..0000000
--- a/ui/gfx/test/data/compositor/string_faded.png
+++ /dev/null
Binary files differ
diff --git a/ui/gfx/test/data/compositor/string_faded_win10.png b/ui/gfx/test/data/compositor/string_faded_win10.png
new file mode 100644
index 0000000..3b91e7a3
--- /dev/null
+++ b/ui/gfx/test/data/compositor/string_faded_win10.png
Binary files differ
diff --git a/ui/gfx/test/data/compositor/string_faded_win7.png b/ui/gfx/test/data/compositor/string_faded_win7.png
new file mode 100644
index 0000000..521a3675
--- /dev/null
+++ b/ui/gfx/test/data/compositor/string_faded_win7.png
Binary files differ
diff --git a/ui/gfx/win/direct_write.cc b/ui/gfx/win/direct_write.cc
index 7bfa86d5b..d500b703 100644
--- a/ui/gfx/win/direct_write.cc
+++ b/ui/gfx/win/direct_write.cc
@@ -17,6 +17,21 @@
 namespace gfx {
 namespace win {
 
+namespace {
+
+// Pointer to the global IDWriteFactory interface.
+IDWriteFactory* g_direct_write_factory = nullptr;
+
+void SetDirectWriteFactory(IDWriteFactory* factory) {
+  DCHECK(!g_direct_write_factory);
+  // We grab a reference on the DirectWrite factory. This reference is
+  // leaked, which is ok because skia leaks it as well.
+  factory->AddRef();
+  g_direct_write_factory = factory;
+}
+
+}  // anonymous namespace
+
 void CreateDWriteFactory(IDWriteFactory** factory) {
   Microsoft::WRL::ComPtr<IUnknown> factory_unknown;
   HRESULT hr =
@@ -40,6 +55,7 @@
   Microsoft::WRL::ComPtr<IDWriteFactory> factory;
   CreateDWriteFactory(factory.GetAddressOf());
   CHECK(!!factory);
+  SetDirectWriteFactory(factory.Get());
 
   // The skia call to create a new DirectWrite font manager instance can fail
   // if we are unable to get the system font collection from the DirectWrite
@@ -50,8 +66,19 @@
       SkFontMgr_New_DirectWrite(factory.Get());
   CHECK(!!direct_write_font_mgr);
 
+  // Override the default skia font manager. This must be called before any
+  // use of the skia font manager is done (e.g. before any call to
+  // SkFontMgr::RefDefault()).
   skia::OverrideDefaultSkFontMgr(std::move(direct_write_font_mgr));
-  gfx::PlatformFontWin::SetDirectWriteFactory(factory.Get());
+}
+
+IDWriteFactory* GetDirectWriteFactory() {
+  // Some unittests may access this accessor without any previous call to
+  // |InitializeDirectWrite|. A call to |InitializeDirectWrite| after this
+  // function being called is still invalid.
+  if (!g_direct_write_factory)
+    InitializeDirectWrite();
+  return g_direct_write_factory;
 }
 
 }  // namespace win
diff --git a/ui/gfx/win/direct_write.h b/ui/gfx/win/direct_write.h
index 63f490bf..b2e33e4 100644
--- a/ui/gfx/win/direct_write.h
+++ b/ui/gfx/win/direct_write.h
@@ -17,6 +17,9 @@
 // Creates a DirectWrite factory.
 GFX_EXPORT void CreateDWriteFactory(IDWriteFactory** factory);
 
+// Returns the global DirectWrite factory.
+GFX_EXPORT IDWriteFactory* GetDirectWriteFactory();
+
 }  // namespace win
 }  // namespace gfx
 
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index a07839da..f2cf183 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -35,8 +35,6 @@
 /** @const */ var SCREEN_WRONG_HWID = 'wrong-hwid';
 /** @const */ var SCREEN_DEVICE_DISABLED = 'device-disabled';
 /** @const */ var SCREEN_UPDATE_REQUIRED = 'update-required';
-/** @const */ var SCREEN_UNRECOVERABLE_CRYPTOHOME_ERROR =
-    'unrecoverable-cryptohome-error';
 /** @const */ var SCREEN_ACTIVE_DIRECTORY_PASSWORD_CHANGE =
     'ad-password-change';
 /** @const */ var SCREEN_SYNC_CONSENT = 'sync-consent';
diff --git a/ui/message_center/views/notification_header_view.cc b/ui/message_center/views/notification_header_view.cc
index 8ef9bbf..c7876d4e 100644
--- a/ui/message_center/views/notification_header_view.cc
+++ b/ui/message_center/views/notification_header_view.cc
@@ -266,11 +266,13 @@
 
 void NotificationHeaderView::SetAppIcon(const gfx::ImageSkia& img) {
   app_icon_view_->SetImage(img);
+  using_default_app_icon_ = false;
 }
 
 void NotificationHeaderView::ClearAppIcon() {
   app_icon_view_->SetImage(
       gfx::CreateVectorIcon(kProductIcon, kSmallImageSizeMD, accent_color_));
+  using_default_app_icon_ = true;
 }
 
 void NotificationHeaderView::SetAppName(const base::string16& name) {
@@ -360,6 +362,19 @@
   summary_text_view_->SetEnabledColor(accent_color_);
   summary_text_divider_->SetEnabledColor(accent_color_);
   SetExpanded(is_expanded_);
+
+  // If we are using the default app icon we should clear it so we refresh it
+  // with the new accent color.
+  if (using_default_app_icon_)
+    ClearAppIcon();
+}
+
+void NotificationHeaderView::SetBackgroundColor(SkColor color) {
+  app_name_view_->SetBackgroundColor(color);
+  summary_text_divider_->SetBackgroundColor(color);
+  summary_text_view_->SetBackgroundColor(color);
+  timestamp_divider_->SetBackgroundColor(color);
+  timestamp_view_->SetBackgroundColor(color);
 }
 
 bool NotificationHeaderView::IsExpandButtonEnabled() {
diff --git a/ui/message_center/views/notification_header_view.h b/ui/message_center/views/notification_header_view.h
index adc254a..4793c326 100644
--- a/ui/message_center/views/notification_header_view.h
+++ b/ui/message_center/views/notification_header_view.h
@@ -41,9 +41,15 @@
   void SetSettingsButtonEnabled(bool enabled);
   void SetCloseButtonEnabled(bool enabled);
   void SetControlButtonsVisible(bool visible);
+
   // Set the unified theme color used among the app icon, app name, and expand
   // button.
   void SetAccentColor(SkColor color);
+
+  // Sets the background color of the notification. This is used to ensure that
+  // the accent color has enough contrast against the background.
+  void SetBackgroundColor(SkColor color);
+
   void ClearAppIcon();
   void ClearProgress();
   void ClearTimestamp();
@@ -86,6 +92,7 @@
   bool has_progress_ = false;
   bool has_timestamp_ = false;
   bool is_expanded_ = false;
+  bool using_default_app_icon_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationHeaderView);
 };
diff --git a/ui/views/controls/editable_combobox/editable_combobox.cc b/ui/views/controls/editable_combobox/editable_combobox.cc
index f911ed4b..6bd81c53 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -31,6 +31,7 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image.h"
+#include "ui/gfx/range/range.h"
 #include "ui/gfx/render_text.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/native_theme/native_theme.h"
@@ -299,6 +300,10 @@
   return style::GetFont(text_context_, text_style_);
 }
 
+void EditableCombobox::SelectRange(const gfx::Range& range) {
+  textfield_->SelectRange(range);
+}
+
 void EditableCombobox::SetAccessibleName(const base::string16& name) {
   textfield_->SetAccessibleName(name);
 }
@@ -356,11 +361,13 @@
 }
 
 void EditableCombobox::OnViewBlurred(View* observed_view) {
-  menu_runner_.reset();
+  CloseMenu();
 }
 
 void EditableCombobox::OnViewFocused(View* observed_view) {
-  ShowDropDownMenu();
+  if (show_menu_on_next_focus_)
+    ShowDropDownMenu();
+  show_menu_on_next_focus_ = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -368,7 +375,7 @@
 
 void EditableCombobox::ButtonPressed(Button* sender, const ui::Event& event) {
   if (menu_runner_ && menu_runner_->IsRunning()) {
-    menu_runner_.reset();
+    CloseMenu();
     return;
   }
   textfield_->RequestFocus();
@@ -383,6 +390,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 // EditableCombobox, Private methods:
 
+void EditableCombobox::CloseMenu() {
+  menu_runner_.reset();
+}
+
 void EditableCombobox::OnItemSelected(int index) {
   // We set |showing_password_text_| to true before calling GetLabelAt on the
   // selected item so that even if it was false we still get the actual
@@ -417,17 +428,13 @@
   menu_model_->UpdateItemsShown();
 }
 
-void EditableCombobox::OnMenuClosed() {
-  menu_runner_.reset();
-}
-
 void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
   constexpr int kMenuBorderWidthLeft = 1;
   constexpr int kMenuBorderWidthTop = 1;
   constexpr int kMenuBorderWidthRight = 1;
 
   if (!menu_model_->GetItemCount()) {
-    menu_runner_.reset();
+    CloseMenu();
     return;
   }
   if (!textfield_->HasFocus() || (menu_runner_ && menu_runner_->IsRunning()))
@@ -446,7 +453,7 @@
 
   menu_runner_ = std::make_unique<MenuRunner>(
       menu_model_.get(), MenuRunner::EDITABLE_COMBOBOX,
-      base::BindRepeating(&EditableCombobox::OnMenuClosed,
+      base::BindRepeating(&EditableCombobox::CloseMenu,
                           base::Unretained(this)));
   menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds,
                           MenuAnchorPosition::kTopLeft, source_type);
diff --git a/ui/views/controls/editable_combobox/editable_combobox.h b/ui/views/controls/editable_combobox/editable_combobox.h
index 2f94b1a..824c6613 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/ui/views/controls/editable_combobox/editable_combobox.h
@@ -20,6 +20,7 @@
 
 namespace gfx {
 class FontList;
+class Range;
 }  // namespace gfx
 
 namespace ui {
@@ -80,6 +81,13 @@
     listener_ = listener;
   }
 
+  void set_show_menu_on_next_focus(bool show_menu_on_next_focus) {
+    show_menu_on_next_focus_ = show_menu_on_next_focus;
+  }
+
+  // Selects the specified logical text range for the textfield.
+  void SelectRange(const gfx::Range& range);
+
   // Sets the accessible name. Use SetAssociatedLabel instead if there is a
   // label associated with this combobox.
   void SetAccessibleName(const base::string16& name);
@@ -102,15 +110,14 @@
  private:
   class EditableComboboxMenuModel;
 
+  void CloseMenu();
+
   // Called when an item is selected from the menu.
   void OnItemSelected(int index);
 
   // Notifies listener of new content and updates the menu items to show.
   void HandleNewContent(const base::string16& new_content);
 
-  // Cleans up after the menu is closed.
-  void OnMenuClosed();
-
   // Shows the drop-down menu.
   void ShowDropDownMenu(ui::MenuSourceType source_type = ui::MENU_SOURCE_NONE);
 
@@ -149,6 +156,10 @@
 
   const Type type_;
 
+  // If false, then the menu won't be shown the next time the View is focused.
+  // Set false on creation to avoid showing the menu on the first focus event.
+  bool show_menu_on_next_focus_ = true;
+
   // Set while the drop-down is showing.
   std::unique_ptr<MenuRunner> menu_runner_;
 
diff --git a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
index 4f03bcb9..b4e18a3 100644
--- a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
@@ -636,5 +636,25 @@
   EXPECT_FALSE(IsMenuOpen());
 }
 
+TEST_F(EditableComboboxTest, ShowMenuOnNextFocusBehavior) {
+  std::vector<base::string16> items = {ASCIIToUTF16("item0"),
+                                       ASCIIToUTF16("item1")};
+  InitEditableCombobox(items, /*filter_on_edit=*/false,
+                       /*show_on_empty=*/true);
+
+  dummy_focusable_view_->RequestFocus();
+  combobox_->set_show_menu_on_next_focus(true);
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+
+  dummy_focusable_view_->RequestFocus();
+  combobox_->set_show_menu_on_next_focus(false);
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_FALSE(IsMenuOpen());
+  dummy_focusable_view_->RequestFocus();
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+}
+
 }  // namespace
 }  // namespace views
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.cc b/ui/views/controls/tabbed_pane/tabbed_pane.cc
index 3b5ade9..93e9c7ba 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -130,7 +130,7 @@
 Tab::Tab(TabbedPane* tabbed_pane, const base::string16& title, View* contents)
     : tabbed_pane_(tabbed_pane),
       title_(new Label(title, style::CONTEXT_LABEL, style::STYLE_TAB_ACTIVE)),
-      tab_state_(TAB_ACTIVE),
+      state_(State::kActive),
       contents_(contents) {
   // Calculate the size while the font list is bold.
   preferred_title_size_ = title_->GetPreferredSize();
@@ -154,7 +154,7 @@
         gfx::Insets(kTabVerticalPadding, kTabHorizontalPadding)));
   }
   SetLayoutManager(std::make_unique<FillLayout>());
-  SetState(TAB_INACTIVE);
+  SetState(State::kInactive);
   // Calculate the size while the font list is normal and set the max size.
   preferred_title_size_.SetToMax(title_->GetPreferredSize());
   AddChildView(title_);
@@ -168,7 +168,7 @@
 
 void Tab::SetSelected(bool selected) {
   contents_->SetVisible(selected);
-  SetState(selected ? TAB_ACTIVE : TAB_INACTIVE);
+  SetState(selected ? State::kActive : State::kInactive);
 #if defined(OS_MACOSX)
   SetFocusBehavior(selected ? FocusBehavior::ACCESSIBLE_ONLY
                             : FocusBehavior::NEVER);
@@ -183,8 +183,8 @@
       tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight;
   const int font_size_delta = is_highlight_mode ? kLabelFontSizeDeltaHighlight
                                                 : ui::kLabelFontSizeDelta;
-  switch (tab_state_) {
-    case TAB_INACTIVE:
+  switch (state_) {
+    case State::kInactive:
       // Notify assistive tools to update this tab's selected status.
       // The way Chrome OS accessibility is implemented right now, firing almost
       // any event will work, we just need to trigger its state to be refreshed.
@@ -197,13 +197,13 @@
                                   is_highlight_mode ? kInactiveWeightHighlight
                                                     : kInactiveWeightBorder));
       break;
-    case TAB_ACTIVE:
+    case State::kActive:
       title_->SetEnabledColor(is_highlight_mode ? kTabTitleColor_ActiveHighlight
                                                 : kTabTitleColor_ActiveBorder);
       title_->SetFontList(rb.GetFontListWithDelta(
           font_size_delta, gfx::Font::NORMAL, kActiveWeight));
       break;
-    case TAB_HOVERED:
+    case State::kHovered:
       title_->SetEnabledColor(kTabTitleColor_Hovered);
       title_->SetFontList(rb.GetFontListWithDelta(
           font_size_delta, gfx::Font::NORMAL,
@@ -219,11 +219,11 @@
 }
 
 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
-  SetState(selected() ? TAB_ACTIVE : TAB_HOVERED);
+  SetState(selected() ? State::kActive : State::kHovered);
 }
 
 void Tab::OnMouseExited(const ui::MouseEvent& event) {
-  SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
+  SetState(selected() ? State::kActive : State::kInactive);
 }
 
 void Tab::OnGestureEvent(ui::GestureEvent* event) {
@@ -235,7 +235,7 @@
       tabbed_pane_->SelectTab(this);
       break;
     case ui::ET_GESTURE_TAP_CANCEL:
-      SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
+      SetState(selected() ? State::kActive : State::kInactive);
       break;
     default:
       break;
@@ -258,10 +258,10 @@
   return kViewClassName;
 }
 
-void Tab::SetState(TabState tab_state) {
-  if (tab_state == tab_state_)
+void Tab::SetState(State state) {
+  if (state == state_)
     return;
-  tab_state_ = tab_state;
+  state_ = state;
   OnStateChanged();
   SchedulePaint();
 }
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.h b/ui/views/controls/tabbed_pane/tabbed_pane.h
index f4a470a..b9101bb 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.h
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.h
@@ -152,17 +152,17 @@
 
   TabbedPane* tabbed_pane() { return tabbed_pane_; }
 
-  // Called whenever |tab_state_| changes.
+  // Called whenever |state_| changes.
   virtual void OnStateChanged();
 
  private:
-  enum TabState {
-    TAB_INACTIVE,
-    TAB_ACTIVE,
-    TAB_HOVERED,
+  enum class State {
+    kInactive,
+    kActive,
+    kHovered,
   };
 
-  void SetState(TabState tab_state);
+  void SetState(State state);
 
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
@@ -170,7 +170,7 @@
   TabbedPane* tabbed_pane_;
   Label* title_;
   gfx::Size preferred_title_size_;
-  TabState tab_state_;
+  State state_;
   // The content view associated with this tab.
   View* contents_;
 
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index b9bc1729..cac8ed0 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -1175,7 +1175,8 @@
   // Changes to the bounds from the server should not consider the min/max.
   const gfx::Rect server_bounds(1, 2, 250, 251);
   static_cast<aura::WindowTreeHostMus*>(widget->GetNativeWindow()->GetHost())
-      ->SetBoundsFromServer(server_bounds, viz::LocalSurfaceIdAllocation());
+      ->SetBoundsFromServer(server_bounds, ui::SHOW_STATE_DEFAULT,
+                            viz::LocalSurfaceIdAllocation());
   EXPECT_EQ(server_bounds, widget->GetWindowBoundsInScreen());
 }
 
diff --git a/ui/views/mus/remote_view/remote_view_provider_unittest.cc b/ui/views/mus/remote_view/remote_view_provider_unittest.cc
index 8499e90..3c08574 100644
--- a/ui/views/mus/remote_view/remote_view_provider_unittest.cc
+++ b/ui/views/mus/remote_view/remote_view_provider_unittest.cc
@@ -208,6 +208,7 @@
   parent_local_surface_id_allocator.GenerateId();
   window_tree_client()->OnWindowBoundsChanged(
       aura::WindowMus::Get(root_window)->server_id(), root_bounds,
+      ui::SHOW_STATE_DEFAULT,
       parent_local_surface_id_allocator.GetCurrentLocalSurfaceIdAllocation());
   EXPECT_EQ(root_bounds, root_window->GetHost()->GetBoundsInPixels());
   EXPECT_EQ(root_bounds.origin(), root_window->GetBoundsInScreen().origin());
diff --git a/ui/webui/resources/cr_elements/chromeos/fingerprint/cr_fingerprint_progress_arc.js b/ui/webui/resources/cr_elements/chromeos/fingerprint/cr_fingerprint_progress_arc.js
index bf7e1c8..c204783 100644
--- a/ui/webui/resources/cr_elements/chromeos/fingerprint/cr_fingerprint_progress_arc.js
+++ b/ui/webui/resources/cr_elements/chromeos/fingerprint/cr_fingerprint_progress_arc.js
@@ -111,6 +111,13 @@
   canvasCircleProgressColor_: CANVAS_CIRCLE_PROGRESS_COLOR,
 
   /**
+   * Animation ID for fingerprint scan progress bar.
+   * @type {number|undefined}
+   * @private
+   */
+  progressAnimationIntervalId_: undefined,
+
+  /**
    * Timer ID for fingerprint scan success update.
    * @type {number|undefined}
    * @private
@@ -166,6 +173,7 @@
       return;
     }
     this.isComplete_ = isComplete;
+    this.cancelAnimations_();
 
     const slice = 2 * Math.PI / 100;
     const startAngle = prevPercentComplete * slice;
@@ -176,41 +184,39 @@
     // The value to update the angle by each tick.
     const step =
         (endAngle - startAngle) / (ANIMATE_DURATION_MS / ANIMATE_TICKS_MS);
-    const id = setInterval(doAnimate.bind(this), ANIMATE_TICKS_MS);
-    // Circles on html canvas have 0 radians on the positive x-axis and go in
-    // clockwise direction. We want to start at the top of the circle which is
-    // 3pi/2.
-    const start = 3 * Math.PI / 2;
-
     // Function that is called every tick of the interval, draws the arc a bit
     // closer to the final destination each tick, until it reaches the final
     // destination.
-    function doAnimate() {
+    const doAnimate = () => {
       if (currentAngle >= endAngle) {
-        clearInterval(id);
+        if (this.progressAnimationIntervalId_) {
+          clearInterval(this.progressAnimationIntervalId_);
+          this.progressAnimationIntervalId_ = undefined;
+        }
         currentAngle = endAngle;
       }
 
       // Clears the canvas and draws the new progress circle.
       this.clearCanvas_();
-      // Drawing two arcs to form a circle gives a nicer look than drawing an
-      // arc on top of a circle. If |currentAngle| is 0, draw from |start| +
-      // |currentAngle| to 7 * Math.PI / 2 (start is 3 * Math.PI / 2) otherwise
-      // the regular draw from |start| to |currentAngle| will draw nothing which
-      // will cause a flicker for one frame.
+      // Drawing two arcs to form a circle gives a nicer look than drawing
+      // an arc on top of a circle. If |currentAngle| is 0, draw from
+      // |start| + |currentAngle| to 7 * Math.PI / 2 (start is 3 * Math.PI /
+      // 2) otherwise the regular draw from |start| to |currentAngle| will
+      // draw nothing which will cause a flicker for one frame.
       this.drawArc(
           start, start + currentAngle, this.canvasCircleProgressColor_);
       this.drawArc(
           start + currentAngle, currentAngle <= 0 ? 7 * Math.PI / 2 : start,
           this.canvasCircleBackgroundColor_);
       currentAngle += step;
-    }
+    };
 
-    // Clean up any pending animation update.
-    if (this.updateTimerId_ !== undefined) {
-      window.clearTimeout(this.updateTimerId_);
-      this.updateTimerId_ = undefined;
-    }
+    this.progressAnimationIntervalId_ =
+        setInterval(doAnimate, ANIMATE_TICKS_MS);
+    // Circles on html canvas have 0 radians on the positive x-axis and go in
+    // clockwise direction. We want to start at the top of the circle which is
+    // 3pi/2.
+    const start = 3 * Math.PI / 2;
 
     if (isComplete) {
       this.animateScanComplete_();
@@ -219,6 +225,21 @@
     }
   },
 
+  /*
+   * Cleans up any pending animation update created by setInterval().
+   * @private
+   */
+  cancelAnimations_: function() {
+    if (this.progressAnimationIntervalId_) {
+      clearInterval(this.progressAnimationIntervalId_);
+      this.progressAnimationIntervalId_ = undefined;
+    }
+    if (this.updateTimerId_) {
+      window.clearTimeout(this.updateTimerId_);
+      this.updateTimerId_ = undefined;
+    }
+  },
+
   /**
    * Show animation for enrollment completion.
    * @private
@@ -259,6 +280,7 @@
    * Reset the element to initial state, when the enrollment just starts.
    */
   reset: function() {
+    this.cancelAnimations_();
     this.clearCanvas_();
     this.isComplete_ = false;
     this.drawBackgroundCircle();
diff --git a/ui/webui/resources/cr_elements/cr_container_shadow_behavior.html b/ui/webui/resources/cr_elements/cr_container_shadow_behavior.html
index d62c681..da6baaf5 100644
--- a/ui/webui/resources/cr_elements/cr_container_shadow_behavior.html
+++ b/ui/webui/resources/cr_elements/cr_container_shadow_behavior.html
@@ -1 +1,3 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <script src="cr_container_shadow_behavior.js"></script>
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.html b/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.html
index 518e6c1..934e036e 100644
--- a/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.html
+++ b/ui/webui/resources/cr_elements/policy/cr_policy_indicator_behavior.html
@@ -1 +1,3 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <script src="cr_policy_indicator_behavior.js"></script>
diff --git a/ui/webui/resources/html/cr/ui/focus_row_behavior.html b/ui/webui/resources/html/cr/ui/focus_row_behavior.html
index 3b50f73f..021fcfb 100644
--- a/ui/webui/resources/html/cr/ui/focus_row_behavior.html
+++ b/ui/webui/resources/html/cr/ui/focus_row_behavior.html
@@ -1,2 +1,5 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <link rel="import" href="focus_row.html">
+
 <script src="../../../js/cr/ui/focus_row_behavior.js"></script>
diff --git a/ui/webui/resources/html/list_property_update_behavior.html b/ui/webui/resources/html/list_property_update_behavior.html
index 3bdf5de..8fa5f3c 100644
--- a/ui/webui/resources/html/list_property_update_behavior.html
+++ b/ui/webui/resources/html/list_property_update_behavior.html
@@ -1 +1,3 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
 <script src="../js/list_property_update_behavior.js"></script>